--- /dev/null
+set(classes
+ vtkDataEncoder
+ vtkObjectIdMap
+ vtkRemoteInteractionAdapter
+ vtkWebApplication
+ vtkWebInteractionEvent
+ vtkWebUtilities)
+
+vtk_module_add_module(VTK::WebCore
+ CLASSES ${classes})
+vtk_add_test_mangling(VTK::WebCore)
--- /dev/null
+if (NOT vtk_testing_cxx_disabled)
+ add_subdirectory(Cxx)
+endif ()
+
+if (VTK_WRAP_PYTHON)
+ vtk_module_test_data(
+ Data/remote_events.json)
+ add_subdirectory(Python)
+endif ()
--- /dev/null
+vtk_add_test_cxx(vtkWebCoreCxxTests tests
+ NO_VALID
+ TestDataEncoder.cxx)
+
+vtk_test_cxx_executable(vtkWebCoreCxxTests tests)
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include <vtkDataEncoder.h>
+#include <vtkImageCast.h>
+#include <vtkImageData.h>
+#include <vtkImageMandelbrotSource.h>
+#include <vtkLogger.h>
+#include <vtkNew.h>
+#include <vtkSmartPointer.h>
+
+#include <chrono>
+#include <thread>
+#include <vector>
+
+vtkSmartPointer<vtkImageData> GetData()
+{
+ vtkNew<vtkImageMandelbrotSource> source;
+ source->SetWholeExtent(0, 256, 0, 256, 0, 0);
+
+ vtkNew<vtkImageCast> caster;
+ caster->SetInputConnection(source->GetOutputPort());
+ caster->SetOutputScalarTypeToUnsignedChar();
+ caster->Update();
+ return caster->GetOutput();
+}
+
+bool TestCreate()
+{
+ vtkLogScopeFunction(INFO);
+ //--------------------------------------------------------------
+ // Create a bunch of instances and ensure it doesn't cause issues
+ // #18344
+ for (int cc = 0; cc < 100; cc++)
+ {
+ vtkNew<vtkDataEncoder> encoder;
+ }
+
+ std::vector<vtkSmartPointer<vtkDataEncoder>> encoders;
+ encoders.reserve(100);
+ for (int cc = 0; cc < 100; cc++)
+ {
+ encoders.push_back(vtk::TakeSmartPointer(vtkDataEncoder::New()));
+ }
+ return true;
+}
+
+bool TestFlush()
+{
+ vtkLogScopeFunction(INFO);
+ constexpr int KEY = 1020;
+
+ vtkNew<vtkDataEncoder> encoder;
+ encoder->SetMaxThreads(5);
+ encoder->Initialize();
+
+ // call flush without pushing any data.
+ encoder->Flush(KEY);
+
+ // push some data and then call flush.
+ for (int cc = 0; cc < 10; cc++)
+ {
+ encoder->Push(KEY, GetData(), 50);
+ }
+
+ encoder->Flush(KEY);
+
+ // call flush again.
+ encoder->Flush(KEY);
+
+ // push some data and then call flush.
+ for (int cc = 0; cc < 10; cc++)
+ {
+ encoder->Push(KEY, GetData(), 50);
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ encoder->Flush(KEY);
+
+ return true;
+}
+
+bool TestLatestOutput()
+{
+ vtkLogScopeFunction(INFO);
+ constexpr int KEY = 1020;
+
+ vtkNew<vtkDataEncoder> encoder;
+
+ vtkSmartPointer<vtkUnsignedCharArray> result;
+ if (encoder->GetLatestOutput(KEY, result))
+ {
+ vtkLogF(ERROR, "no output expected!");
+ return false;
+ }
+
+ // push some data and then call flush.
+ for (int cc = 0; cc < 10; cc++)
+ {
+ encoder->Push(KEY, GetData(), 50);
+ }
+
+ encoder->Flush(KEY);
+ if (!encoder->GetLatestOutput(KEY, result))
+ {
+ vtkLogF(ERROR, "latest output expected!");
+ return false;
+ }
+
+ return true;
+}
+
+int TestDataEncoder(int /*argc*/, char* /*argv*/[])
+{
+ TestCreate();
+ TestFlush();
+ TestLatestOutput();
+ return EXIT_SUCCESS;
+}
--- /dev/null
+19d5717b868631051688354258802885866eaf6fe3bbbdcd2e8410c2aa1b65ff4585d6943c751b54aa54d6b9c8fe50d97bcbf0c061c5fa63959a3d7e653abe0e
--- /dev/null
+9ef24c779e9c2176cfa88c195e3a8f1102bb3ad03593594e84c5c12f785100ac037e180ad7e56726874433bf2b72fe8e72569222de252ae45f4c224c57fd1fe3
--- /dev/null
+b05dd96550cd5a74ed49277cdf5efd635ef5a53b34152c4af90b0f4d007b3d2c46c3a450cf759cea36c2b84efdd3946853198b423bde8ac9ace8deaff446e258
--- /dev/null
+vtk_add_test_python(
+ TestDataEncoder.py
+ TestRemoteInteractionAdapter.py
+ )
+
+vtk_add_test_python(
+ NO_DATA NO_VALID NO_OUTPUT
+ TestObjectIdMap.py
+ TestWebApplicationMemory.py
+ )
--- /dev/null
+import sys
+from vtkmodules.vtkFiltersSources import vtkCylinderSource
+from vtkmodules.vtkIOCore import vtkBase64Utilities
+from vtkmodules.vtkRenderingCore import (
+ vtkActor,
+ vtkPolyDataMapper,
+ vtkRenderWindow,
+ vtkRenderer,
+ vtkWindowToImageFilter,
+)
+from vtkmodules.vtkTestingRendering import vtkTesting
+from vtkmodules.vtkWebCore import vtkDataEncoder
+import vtkmodules.vtkRenderingFreeType
+import vtkmodules.vtkRenderingOpenGL2
+import array
+from vtkmodules.test import Testing
+
+
+class TestDataEncoder(Testing.vtkTest):
+ def testEncodings(self):
+ # Render something
+ cylinder = vtkCylinderSource()
+ cylinder.SetResolution(8)
+
+ cylinderMapper = vtkPolyDataMapper()
+ cylinderMapper.SetInputConnection(cylinder.GetOutputPort())
+
+ cylinderActor = vtkActor()
+ cylinderActor.SetMapper(cylinderMapper)
+ cylinderActor.RotateX(30.0)
+ cylinderActor.RotateY(-45.0)
+
+ ren = vtkRenderer()
+ renWin = vtkRenderWindow()
+ renWin.AddRenderer(ren)
+ ren.AddActor(cylinderActor)
+ renWin.SetSize(200, 200)
+
+ ren.ResetCamera()
+ ren.GetActiveCamera().Zoom(1.5)
+ renWin.Render()
+
+ # Get a vtkImageData with the rendered output
+ w2if = vtkWindowToImageFilter()
+ w2if.SetInput(renWin)
+ w2if.SetShouldRerender(1)
+ w2if.SetReadFrontBuffer(0)
+ w2if.Update()
+ imgData = w2if.GetOutput()
+
+ # Use vtkDataEncoder to convert the image to PNG format and Base64 encode it
+ encoder = vtkDataEncoder()
+ base64String = encoder.EncodeAsBase64Png(imgData).encode('ascii')
+
+ # Now Base64 decode the string back to PNG image data bytes
+ inputArray = array.array('B', base64String)
+ outputBuffer = bytearray(len(inputArray))
+
+ try:
+ utils = vtkBase64Utilities()
+ except:
+ print('Unable to import required vtkBase64Utilities')
+ raise Exception("TestDataEncoder failed.")
+
+ actualLength = utils.DecodeSafely(inputArray, len(inputArray), outputBuffer, len(outputBuffer))
+ outputArray = bytearray(actualLength)
+ outputArray[:] = outputBuffer[0:actualLength]
+
+ # And write those bytes to the disk as an actual PNG image file
+ with open('TestDataEncoder.png', 'wb') as fd:
+ fd.write(outputArray)
+
+ # Create a vtkTesting object and specify a baseline image
+ rtTester = vtkTesting()
+ for arg in sys.argv[1:]:
+ rtTester.AddArgument(arg)
+ rtTester.AddArgument("-V")
+ rtTester.AddArgument("TestDataEncoder.png")
+
+ # Perform the image comparison test and print out the result.
+ result = rtTester.RegressionTest("TestDataEncoder.png", 0.05)
+
+ if result == 0:
+ raise Exception("TestDataEncoder failed.")
+
+if __name__ == "__main__":
+ Testing.main([(TestDataEncoder, 'test')])
--- /dev/null
+from vtkmodules.vtkCommonCore import vtkObject
+from vtkmodules.vtkWebCore import vtkObjectIdMap
+from vtkmodules.test import Testing
+from vtkmodules.vtkWebCore import vtkWebApplication
+
+class TestObjectId(Testing.vtkTest):
+ def testObjId(self):
+ map = vtkObjectIdMap()
+ # Just make sure if we call it twice with None, the results match
+ objId1 = map.GetGlobalId(None)
+ objId1b = map.GetGlobalId(None)
+ print('Object ids for None: objId1 => ',objId1,', objId1b => ',objId1b)
+ self.assertTrue(objId1 == objId1b)
+
+ object2 = vtkObject()
+ addr2 = object2.__this__
+ addr2 = addr2[1:addr2.find('_', 1)]
+ addr2 = int(addr2, 16)
+
+ object3 = vtkObject()
+ addr3 = object3.__this__
+ addr3 = addr3[1:addr3.find('_', 1)]
+ addr3 = int(addr3, 16)
+
+ # insert the bigger address first
+ if (addr2 < addr3):
+ object2, object3 = object3, object2
+
+ objId2 = map.GetGlobalId(object2)
+ objId2b = map.GetGlobalId(object2)
+ print('Object ids for object2: objId2 => ',objId2,', objId2b => ',objId2b)
+ self.assertTrue(objId2 == objId2b)
+
+ objId3 = map.GetGlobalId(object3)
+ objId3b = map.GetGlobalId(object3)
+ print('Object ids for object3: objId3 => ',objId3,', objId3b => ',objId3b)
+ self.assertTrue(objId3 == objId3b)
+
+if __name__ == "__main__":
+ Testing.main([(TestObjectId, 'test')])
--- /dev/null
+# SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+# SPDX-License-Identifier: BSD-3-Clause
+
+""" Apply a series of events produced by vtk-js RenderWindowInteractor to a
+vtkRenderwindow via the vtkRemoteInteractionAdapter class. The final image is
+the expected scene after all interactions have been applied.
+"""
+
+from vtkmodules.vtkRenderingCore import (
+ vtkActor,
+ vtkPolyDataMapper,
+ vtkRenderer,
+ vtkRenderWindow,
+ vtkRenderWindowInteractor,
+)
+from vtkmodules.vtkWebCore import vtkRemoteInteractionAdapter
+from vtkmodules.vtkFiltersSources import vtkConeSource
+
+from vtkmodules.test import Testing
+import os
+import json
+
+# Required for rendering initialization,
+import vtkmodules.vtkRenderingOpenGL2 # noqa
+
+# Required for interactor initialization
+from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa
+
+
+# The scene to test. In some platforms reusing the window & renderer across
+# the two test cases causes segfault. We start all tests from a clean state by
+# creating the scene from scratch each time.
+class Scene:
+ def __init__(self):
+ self.dataFile = os.path.join(
+ Testing.VTK_DATA_ROOT, "Data", "remote_events.json"
+ )
+ self.imageFile = "TestRemoteInteractionAdapter.png"
+ self.adapter = vtkRemoteInteractionAdapter()
+
+ print("dataFile: {}".format(self.dataFile))
+ if not os.path.isfile(self.dataFile):
+ raise RuntimeError("Datafile is missing")
+
+ self.renderer = vtkRenderer()
+ self.renderWindow = vtkRenderWindow()
+ self.renderWindow.AddRenderer(self.renderer)
+ self.renderWindow.SetSize(300, 300)
+
+ self.renderWindowInteractor = vtkRenderWindowInteractor()
+ self.renderWindowInteractor.SetRenderWindow(self.renderWindow)
+ self.renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
+
+ self.cone_source = vtkConeSource()
+ self.mapper = vtkPolyDataMapper()
+ self.mapper.SetInputConnection(self.cone_source.GetOutputPort())
+ self.actor = vtkActor()
+ self.actor.SetMapper(self.mapper)
+
+ self.renderer.AddActor(self.actor)
+ self.renderer.ResetCamera()
+ self.renderWindowInteractor.Initialize()
+
+
+class TestRemoteInteractorAdapter(Testing.vtkTest):
+ def test0(self):
+ """Use class methods API for ProcessEvent"""
+ scene = Scene()
+
+ adapter = vtkRemoteInteractionAdapter()
+ adapter.SetInteractor(scene.renderWindowInteractor)
+
+ with open(scene.dataFile, "r") as f:
+ data = json.load(f)
+ for event in data["events"]:
+ event_str = json.dumps(event)
+ status = adapter.ProcessEvent(event_str)
+ assert status, f"Failed to process event\n {event_str}"
+ scene.renderWindowInteractor.Render()
+ self.assertImageMatch(scene.renderWindow, scene.imageFile)
+
+ def test1(self):
+ """Use static method API for ProcessEvent"""
+ scene = Scene()
+
+ with open(scene.dataFile, "r") as f:
+ data = json.load(f)
+ for event in data["events"]:
+ event_str = json.dumps(event)
+ status = vtkRemoteInteractionAdapter.ProcessEvent(
+ scene.renderWindowInteractor, event_str
+ )
+ assert status, f"Failed to process event\n {event_str}"
+ scene.renderWindowInteractor.Render()
+ self.assertImageMatch(scene.renderWindow, scene.imageFile)
+
+
+if __name__ == "__main__":
+ Testing.main([(TestRemoteInteractorAdapter, "test")])
--- /dev/null
+from vtkmodules.vtkFiltersSources import vtkCylinderSource
+from vtkmodules.vtkRenderingCore import (
+ vtkActor,
+ vtkPolyDataMapper,
+ vtkRenderWindow,
+ vtkRenderer,
+)
+from vtkmodules.vtkWebCore import vtkWebApplication
+import vtkmodules.vtkRenderingFreeType
+import vtkmodules.vtkRenderingOpenGL2
+from vtkmodules.test import Testing
+from vtkmodules.vtkWebCore import vtkWebApplication
+
+class TestWebApplicationMemory(Testing.vtkTest):
+ def testWebApplicationMemory(self):
+ cylinder = vtkCylinderSource()
+ cylinder.SetResolution(8)
+
+ cylinderMapper = vtkPolyDataMapper()
+ cylinderMapper.SetInputConnection(cylinder.GetOutputPort())
+
+ cylinderActor = vtkActor()
+ cylinderActor.SetMapper(cylinderMapper)
+ cylinderActor.RotateX(30.0)
+ cylinderActor.RotateY(-45.0)
+
+ ren = vtkRenderer()
+ renWin = vtkRenderWindow()
+ renWin.AddRenderer(ren)
+ ren.AddActor(cylinderActor)
+ renWin.SetSize(200, 200)
+
+ ren.ResetCamera()
+ ren.GetActiveCamera().Zoom(1.5)
+ renWin.Render()
+
+ webApp = vtkWebApplication()
+ # no memory leaks should be reported when compiling with VTK_DEBUG_LEAKS
+ webApp.StillRender(renWin)
+
+if __name__ == "__main__":
+ Testing.main([(TestWebApplicationMemory, 'test')])
--- /dev/null
+NAME
+ VTK::WebCore
+LIBRARY_NAME
+ vtkWebCore
+GROUPS
+ Web
+SPDX_LICENSE_IDENTIFIER
+ BSD-3-Clause
+SPDX_COPYRIGHT_TEXT
+ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+DEPENDS
+ VTK::CommonCore
+PRIVATE_DEPENDS
+ VTK::CommonDataModel
+ VTK::CommonSystem
+ VTK::FiltersGeneral
+ VTK::FiltersGeometry
+ VTK::IOCore
+ VTK::IOImage
+ VTK::ParallelCore
+ VTK::Python
+ VTK::RenderingCore
+ VTK::WebGLExporter
+ VTK::vtksys
+ VTK::nlohmannjson
+TEST_LABELS
+ VTK::Web
+TEST_DEPENDS
+ VTK::ImagingCore
+ VTK::ImagingSources
+ VTK::TestingCore
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkDataEncoder.h"
+
+#include "vtkBase64Utilities.h"
+#include "vtkCommand.h"
+#include "vtkImageData.h"
+#include "vtkJPEGWriter.h"
+#include "vtkLogger.h"
+#include "vtkNew.h"
+#include "vtkObjectFactory.h"
+#include "vtkPNGWriter.h"
+#include "vtkSmartPointer.h"
+#include "vtkUnsignedCharArray.h"
+
+#include <cassert>
+#include <cmath>
+#include <condition_variable>
+#include <map>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <vector>
+
+#include <vtksys/SystemTools.hxx>
+
+#define MAX_NUMBER_OF_THREADS_IN_POOL 32
+
+namespace detail
+{
+VTK_ABI_NAMESPACE_BEGIN
+
+struct vtkWork
+{
+ vtkSmartPointer<vtkImageData> Image;
+ int Quality = 0;
+ int Encoding = 0;
+ vtkTypeUInt64 TimeStamp = 0;
+ vtkTypeUInt32 Key = 0;
+
+ vtkWork() = default;
+ vtkWork(vtkTypeUInt32 key, vtkImageData* image, int quality, int encoding)
+ : Image(image)
+ , Quality(quality)
+ , Encoding(encoding)
+ , TimeStamp(0)
+ , Key(key)
+ {
+ }
+ vtkWork(const vtkWork&) = default;
+ vtkWork& operator=(const vtkWork&) = default;
+};
+
+class vtkWorkQueue
+{
+ mutable std::mutex ResultsMutex;
+ std::map<vtkTypeUInt32, std::pair<vtkTypeUInt64, vtkSmartPointer<vtkUnsignedCharArray>>> Results;
+ std::condition_variable ResultsCondition;
+
+ std::map<vtkTypeUInt32, std::atomic<vtkTypeUInt32>> LastTimeStamp;
+
+ std::mutex QueueMutex;
+ std::queue<vtkWork> Queue;
+ std::condition_variable QueueCondition;
+
+ std::vector<std::thread> ThreadPool;
+ std::atomic<bool> Terminate;
+
+ static void DoWork(int threadIndex, vtkWorkQueue* self)
+ {
+ vtkLogger::SetThreadName("Worker " + std::to_string(threadIndex));
+ vtkLogF(TRACE, "starting worker thread");
+ vtkNew<vtkJPEGWriter> writer;
+ writer->WriteToMemoryOn();
+ while (!self->Terminate)
+ {
+ vtkWork work;
+ {
+ std::unique_lock<std::mutex> lock(self->QueueMutex);
+ bool break_loop = false;
+ do
+ {
+ self->QueueCondition.wait_for(lock, std::chrono::seconds(1),
+ [self]() { return !self->Queue.empty() || self->Terminate; });
+ if (self->Terminate)
+ {
+ break_loop = true;
+ break;
+ }
+ } while (self->Queue.empty());
+ if (break_loop)
+ {
+ break;
+ }
+ work = self->Queue.front();
+ self->Queue.pop();
+ }
+
+ writer->SetInputData(work.Image);
+ writer->SetQuality(work.Quality);
+ writer->Write();
+
+ auto result = vtkSmartPointer<vtkUnsignedCharArray>::New();
+ if (work.Encoding)
+ {
+ vtkUnsignedCharArray* data = writer->GetResult();
+ result->SetNumberOfComponents(1);
+ result->SetNumberOfTuples(std::ceil(1.5 * data->GetNumberOfTuples()));
+ unsigned long size = vtkBase64Utilities::Encode(
+ data->GetPointer(0), data->GetNumberOfTuples(), result->GetPointer(0), /*mark_end=*/0);
+ result->SetNumberOfTuples(static_cast<vtkIdType>(size) + 1);
+ result->SetValue(size, 0);
+ }
+ else
+ {
+ // We must do a deep copy here as the writer reuse that array
+ // and will change its values concurrently during its next job...
+ result->DeepCopy(writer->GetResult());
+ }
+ writer->SetInputData(nullptr);
+
+ {
+ std::unique_lock<std::mutex> lock(self->ResultsMutex);
+ auto& pair = self->Results[work.Key];
+ if (pair.first < work.TimeStamp)
+ {
+ pair = std::make_pair(work.TimeStamp, result);
+ lock.unlock();
+ self->ResultsCondition.notify_all();
+ }
+ }
+ }
+
+ vtkLogF(TRACE, "exiting worker thread");
+ }
+
+public:
+ vtkWorkQueue(int numThreads)
+ : Terminate(false)
+ {
+ assert(numThreads >= 0);
+ for (int cc = 0; cc < numThreads; ++cc)
+ {
+ this->ThreadPool.emplace_back(&vtkWorkQueue::DoWork, cc, this);
+ }
+ }
+ ~vtkWorkQueue()
+ {
+ this->Terminate = true;
+ this->QueueCondition.notify_all();
+ for (auto& thread : this->ThreadPool)
+ {
+ thread.join();
+ }
+ }
+
+ bool IsValid() const { return !this->ThreadPool.empty(); }
+
+ void PushBack(vtkWork&& work)
+ {
+ if (!this->IsValid())
+ {
+ vtkLogF(ERROR, "Queue is invalid! Can't push work!");
+ return;
+ }
+
+ auto key = work.Key;
+ work.TimeStamp = ++this->LastTimeStamp[key];
+ {
+ std::unique_lock<std::mutex> lock(this->QueueMutex);
+ this->Queue.emplace(std::move(work));
+ }
+ this->QueueCondition.notify_one();
+ }
+
+ bool GetResult(vtkTypeUInt32 key, vtkSmartPointer<vtkUnsignedCharArray>& data) const
+ {
+ std::unique_lock<std::mutex> lock(this->ResultsMutex);
+ auto iter = this->Results.find(key);
+ if (iter == this->Results.end())
+ {
+ return false;
+ }
+
+ const auto& resultsPair = iter->second;
+ data = resultsPair.second;
+ // return true if this is the latest result for this key.
+ return (resultsPair.first == this->LastTimeStamp.at(key));
+ }
+
+ void Flush(vtkTypeUInt32 key)
+ {
+ auto tsIter = this->LastTimeStamp.find(key);
+ if (tsIter == this->LastTimeStamp.end())
+ {
+ return;
+ }
+ const auto& ts = tsIter->second;
+ std::unique_lock<std::mutex> lock(this->ResultsMutex);
+ this->ResultsCondition.wait(lock,
+ [this, &ts, &key]()
+ {
+ try
+ {
+ return ts == this->Results[key].first;
+ }
+ catch (std::out_of_range&)
+ {
+ // result not available yet; keep waiting;
+ return false;
+ }
+ });
+ }
+};
+VTK_ABI_NAMESPACE_END
+} // namespace detail
+
+VTK_ABI_NAMESPACE_BEGIN
+//****************************************************************************
+class vtkDataEncoder::vtkInternals
+{
+public:
+ detail::vtkWorkQueue Queue;
+ vtkNew<vtkUnsignedCharArray> LastBase64Image;
+
+ vtkInternals(int numThreads)
+ : Queue(numThreads)
+ {
+ }
+
+ // Once an imagedata has been written to memory as a jpg or png, this
+ // convenience function can encode that image as a Base64 string.
+ const char* GetBase64EncodedImage(vtkUnsignedCharArray* encodedInputImage)
+ {
+ this->LastBase64Image->SetNumberOfComponents(1);
+ this->LastBase64Image->SetNumberOfTuples(
+ std::ceil(1.5 * encodedInputImage->GetNumberOfTuples()));
+ unsigned long size = vtkBase64Utilities::Encode(encodedInputImage->GetPointer(0),
+ encodedInputImage->GetNumberOfTuples(), this->LastBase64Image->GetPointer(0), /*mark_end=*/0);
+
+ this->LastBase64Image->SetNumberOfTuples(static_cast<vtkIdType>(size) + 1);
+ this->LastBase64Image->SetValue(size, 0);
+
+ return reinterpret_cast<char*>(this->LastBase64Image->GetPointer(0));
+ }
+};
+
+vtkStandardNewMacro(vtkDataEncoder);
+//------------------------------------------------------------------------------
+vtkDataEncoder::vtkDataEncoder()
+ : MaxThreads(3)
+ , Internals(new vtkInternals(this->MaxThreads))
+{
+}
+
+//------------------------------------------------------------------------------
+vtkDataEncoder::~vtkDataEncoder() = default;
+
+//------------------------------------------------------------------------------
+void vtkDataEncoder::SetMaxThreads(vtkTypeUInt32 maxThreads)
+{
+ if (maxThreads < MAX_NUMBER_OF_THREADS_IN_POOL && maxThreads > 0)
+ {
+ this->MaxThreads = maxThreads;
+ }
+}
+
+//------------------------------------------------------------------------------
+void vtkDataEncoder::Initialize()
+{
+ this->Internals.reset(new vtkDataEncoder::vtkInternals(this->MaxThreads));
+}
+
+//------------------------------------------------------------------------------
+void vtkDataEncoder::Push(vtkTypeUInt32 key, vtkImageData* data, int quality, int encoding)
+{
+ auto& internals = (*this->Internals);
+ internals.Queue.PushBack(detail::vtkWork(key, data, quality, encoding));
+}
+
+//------------------------------------------------------------------------------
+bool vtkDataEncoder::GetLatestOutput(vtkTypeUInt32 key, vtkSmartPointer<vtkUnsignedCharArray>& data)
+{
+ auto& internals = (*this->Internals);
+ return internals.Queue.GetResult(key, data);
+}
+
+//------------------------------------------------------------------------------
+const char* vtkDataEncoder::EncodeAsBase64Png(vtkImageData* img, int compressionLevel)
+{
+ // Perform in-memory write of image as png
+ vtkNew<vtkPNGWriter> writer;
+ writer->WriteToMemoryOn();
+ writer->SetInputData(img);
+ writer->SetCompressionLevel(compressionLevel);
+ writer->Write();
+
+ // Return Base64-encoded string
+ return this->Internals->GetBase64EncodedImage(writer->GetResult());
+}
+
+//------------------------------------------------------------------------------
+const char* vtkDataEncoder::EncodeAsBase64Jpg(vtkImageData* img, int quality)
+{
+ // Perform in-memory write of image as jpg
+ vtkNew<vtkJPEGWriter> writer;
+ writer->WriteToMemoryOn();
+ writer->SetInputData(img);
+ writer->SetQuality(quality);
+ writer->Write();
+
+ // Return Base64-encoded string
+ return this->Internals->GetBase64EncodedImage(writer->GetResult());
+}
+
+//------------------------------------------------------------------------------
+void vtkDataEncoder::Flush(vtkTypeUInt32 key)
+{
+ auto& internals = (*this->Internals);
+ internals.Queue.Flush(key);
+}
+
+//------------------------------------------------------------------------------
+void vtkDataEncoder::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+//------------------------------------------------------------------------------
+void vtkDataEncoder::Finalize()
+{
+ this->Internals.reset(new vtkDataEncoder::vtkInternals(0));
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkDataEncoder
+ * @brief class used to compress/encode images using threads.
+ *
+ * vtkDataEncoder is used to compress and encode images using threads.
+ * Multiple images can be pushed into the encoder for compression and encoding.
+ * We use a vtkTypeUInt32 as the key to identify different image pipes. The
+ * images in each pipe will be processed in parallel threads. The latest
+ * compressed and encoded image can be accessed using GetLatestOutput().
+ *
+ * vtkDataEncoder uses a thread-pool to do the compression and encoding in
+ * parallel. Note that images may not come out of the vtkDataEncoder in the
+ * same order as they are pushed in, if an image pushed in at N-th location
+ * takes longer to compress and encode than that pushed in at N+1-th location or
+ * if it was pushed in before the N-th location was even taken up for encoding
+ * by the a thread in the thread pool.
+ */
+
+#ifndef vtkDataEncoder_h
+#define vtkDataEncoder_h
+
+#include "vtkObject.h"
+#include "vtkSmartPointer.h" // needed for vtkSmartPointer
+#include "vtkWebCoreModule.h" // needed for exports
+#include <memory> // for std::unique_ptr
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkUnsignedCharArray;
+class vtkImageData;
+
+class VTKWEBCORE_EXPORT vtkDataEncoder : public vtkObject
+{
+public:
+ static vtkDataEncoder* New();
+ vtkTypeMacro(vtkDataEncoder, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ ///@{
+ /**
+ * Define the number of worker threads to use. Default is 3.
+ * Initialize() needs to be called after changing the thread count.
+ */
+ void SetMaxThreads(vtkTypeUInt32);
+ vtkGetMacro(MaxThreads, vtkTypeUInt32);
+ ///@}
+
+ /**
+ * Re-initializes the encoder. This will abort any on going encoding threads
+ * and clear internal data-structures.
+ */
+ void Initialize();
+
+ /**
+ * Push an image into the encoder. The data is considered unchanging and thus
+ * should not be modified once pushed. Reference count changes are now thread safe
+ * and hence callers should ensure they release the reference held, if
+ * appropriate.
+ */
+ void Push(vtkTypeUInt32 key, vtkImageData* data, int quality, int encoding = 1);
+
+ /**
+ * Get access to the most-recent fully encoded result corresponding to the
+ * given key, if any. This methods returns true if the \c data obtained is the
+ * result from the most recent Push() for the key, if any. If this method
+ * returns false, it means that there's some image either being processed on
+ * pending processing.
+ */
+ bool GetLatestOutput(vtkTypeUInt32 key, vtkSmartPointer<vtkUnsignedCharArray>& data);
+
+ /**
+ * Flushes the encoding pipe and blocks till the most recently pushed image
+ * for the particular key has been processed. This call will block. Once this
+ * method returns, caller can use GetLatestOutput(key) to access the processed
+ * output.
+ */
+ void Flush(vtkTypeUInt32 key);
+
+ /**
+ * Take an image data and synchronously convert it to a base-64 encoded png.
+ */
+ const char* EncodeAsBase64Png(vtkImageData* img, int compressionLevel = 5);
+
+ /**
+ * Take an image data and synchronously convert it to a base-64 encoded jpg.
+ */
+ const char* EncodeAsBase64Jpg(vtkImageData* img, int quality = 50);
+
+ /**
+ * This method will wait for any running thread to terminate.
+ */
+ void Finalize();
+
+protected:
+ vtkDataEncoder();
+ ~vtkDataEncoder() override;
+
+ vtkTypeUInt32 MaxThreads;
+
+private:
+ vtkDataEncoder(const vtkDataEncoder&) = delete;
+ void operator=(const vtkDataEncoder&) = delete;
+
+ class vtkInternals;
+ std::unique_ptr<vtkInternals> Internals;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkObjectIdMap.h"
+
+#include "vtkObjectFactory.h"
+#include "vtkSmartPointer.h"
+#include "vtkWeakPointer.h"
+
+#include <map>
+#include <set>
+#include <string>
+
+VTK_ABI_NAMESPACE_BEGIN
+struct vtkObjectIdMap::vtkInternals
+{
+ std::map<vtkTypeUInt32, vtkSmartPointer<vtkObject>> Object;
+ std::map<vtkSmartPointer<vtkObject>, vtkTypeUInt32> GlobalId;
+ std::map<std::string, vtkWeakPointer<vtkObject>> ActiveObjects;
+ vtkTypeUInt32 NextAvailableId;
+
+ vtkInternals()
+ : NextAvailableId(1)
+ {
+ }
+};
+
+vtkStandardNewMacro(vtkObjectIdMap);
+//------------------------------------------------------------------------------
+vtkObjectIdMap::vtkObjectIdMap()
+ : Internals(new vtkInternals())
+{
+}
+
+//------------------------------------------------------------------------------
+vtkObjectIdMap::~vtkObjectIdMap()
+{
+ delete this->Internals;
+ this->Internals = nullptr;
+}
+
+//------------------------------------------------------------------------------
+void vtkObjectIdMap::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+//------------------------------------------------------------------------------
+vtkTypeUInt32 vtkObjectIdMap::GetGlobalId(vtkObject* obj)
+{
+ if (obj == nullptr)
+ {
+ return 0;
+ }
+
+ auto iter = this->Internals->GlobalId.find(obj);
+ if (iter == this->Internals->GlobalId.end())
+ {
+ vtkTypeUInt32 globalId = this->Internals->NextAvailableId++;
+ this->Internals->GlobalId[obj] = globalId;
+ this->Internals->Object[globalId] = obj;
+ return globalId;
+ }
+ return iter->second;
+}
+
+//------------------------------------------------------------------------------
+vtkObject* vtkObjectIdMap::GetVTKObject(vtkTypeUInt32 globalId)
+{
+ auto iter = this->Internals->Object.find(globalId);
+ if (iter == this->Internals->Object.end())
+ {
+ return nullptr;
+ }
+ return iter->second;
+}
+
+//------------------------------------------------------------------------------
+vtkTypeUInt32 vtkObjectIdMap::SetActiveObject(const char* objectType, vtkObject* obj)
+{
+ if (objectType)
+ {
+ this->Internals->ActiveObjects[objectType] = obj;
+ return this->GetGlobalId(obj);
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------------
+vtkObject* vtkObjectIdMap::GetActiveObject(const char* objectType)
+{
+ if (objectType)
+ {
+ return this->Internals->ActiveObjects[objectType];
+ }
+ return nullptr;
+}
+
+//------------------------------------------------------------------------------
+bool vtkObjectIdMap::FreeObject(vtkObject* obj)
+{
+ auto iter = this->Internals->GlobalId.find(obj);
+ auto found = iter != this->Internals->GlobalId.end();
+ if (found)
+ {
+ this->Internals->Object.erase(iter->second);
+ this->Internals->GlobalId.erase(iter);
+ }
+ return found;
+}
+
+//------------------------------------------------------------------------------
+bool vtkObjectIdMap::FreeObjectById(vtkTypeUInt32 id)
+{
+ auto iter = this->Internals->Object.find(id);
+ auto found = iter != this->Internals->Object.end();
+ if (found)
+ {
+ this->Internals->GlobalId.erase(iter->second);
+ this->Internals->Object.erase(iter);
+ }
+ return found;
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkObjectIdMap
+ * @brief class used to assign Id to any VTK object and be able
+ * to retrieve it base on its id.
+ */
+
+#ifndef vtkObjectIdMap_h
+#define vtkObjectIdMap_h
+
+#include "vtkObject.h"
+#include "vtkWebCoreModule.h" // needed for exports
+
+VTK_ABI_NAMESPACE_BEGIN
+class VTKWEBCORE_EXPORT vtkObjectIdMap : public vtkObject
+{
+public:
+ static vtkObjectIdMap* New();
+ vtkTypeMacro(vtkObjectIdMap, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ /**
+ * Retrieve a unique identifier for the given object or generate a new one
+ * if its global id was never requested.
+ */
+ vtkTypeUInt32 GetGlobalId(vtkObject* obj);
+
+ /**
+ * Retrieve a vtkObject based on its global id. If not found return nullptr
+ */
+ vtkObject* GetVTKObject(vtkTypeUInt32 globalId);
+
+ /**
+ * Assign an active key (string) to an existing object.
+ * This is usually used to provide another type of access to specific
+ * vtkObject that we want to retrieve easily using a string.
+ * Return the global Id of the given registered object
+ */
+ vtkTypeUInt32 SetActiveObject(const char* objectType, vtkObject* obj);
+
+ /**
+ * Retrieve a previously stored object based on a name
+ */
+ vtkObject* GetActiveObject(const char* objectType);
+
+ /**
+ * Given an object, remove any internal reference count due to
+ * internal Id/Object mapping.
+ * Returns true if the item existed in the map and was deleted.
+ */
+ bool FreeObject(vtkObject* obj);
+
+ /**
+ * Given an id, remove any internal reference count due to
+ * internal Id/Object mapping.
+ * Returns true if the id existed in the map and was deleted.
+ */
+ bool FreeObjectById(vtkTypeUInt32 id);
+
+protected:
+ vtkObjectIdMap();
+ ~vtkObjectIdMap() override;
+
+private:
+ vtkObjectIdMap(const vtkObjectIdMap&) = delete;
+ void operator=(const vtkObjectIdMap&) = delete;
+
+ struct vtkInternals;
+ vtkInternals* Internals;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "vtkRemoteInteractionAdapter.h"
+#include "vtkCommand.h"
+#include "vtkLogger.h"
+#include "vtkObjectFactory.h"
+#include "vtkRenderWindow.h"
+#include "vtkRenderWindowInteractor.h"
+
+#include <vtk_nlohmannjson.h>
+#include VTK_NLOHMANN_JSON(json.hpp)
+
+#include <unordered_map>
+
+VTK_ABI_NAMESPACE_BEGIN
+
+enum
+{
+ WheelEvent = vtkCommand::UserEvent + 3000,
+};
+
+// map vtk-js event codes to vtkCommand events , for the ones that I didn't found a clear
+// correspondence I used vtkCommand::NoEvent and left them unhandled. Taken from
+// https://github.com/Kitware/vtk-js/blob/master/Sources/Rendering/Core/RenderWindowInteractor/index.js
+
+using enum_type = int;
+// clang-format off
+const std::unordered_map< std::string, enum_type > EVENT_MAP {
+ {"StartAnimation" ,vtkCommand::NoEvent},
+ {"Animation" ,vtkCommand::NoEvent},
+ {"EndAnimation" ,vtkCommand::NoEvent},
+ {"PointerEnter" ,vtkCommand::EnterEvent},
+ {"PointerLeave" ,vtkCommand::LeaveEvent},
+ {"MouseEnter" ,vtkCommand::EnterEvent},
+ {"MouseLeave" ,vtkCommand::LeaveEvent},
+ {"StartMouseMove" ,vtkCommand::NoEvent},
+ {"MouseMove" ,vtkCommand::MouseMoveEvent},
+ {"EndMouseMove" ,vtkCommand::NoEvent},
+ {"LeftButtonPress" ,vtkCommand::LeftButtonPressEvent},
+ {"LeftButtonRelease" ,vtkCommand::LeftButtonReleaseEvent},
+ {"MiddleButtonPress" ,vtkCommand::MiddleButtonPressEvent},
+ {"MiddleButtonRelease" ,vtkCommand::MiddleButtonReleaseEvent},
+ {"RightButtonPress" ,vtkCommand::RightButtonPressEvent},
+ {"RightButtonRelease" ,vtkCommand::RightButtonReleaseEvent},
+ {"KeyPress" ,vtkCommand::KeyPressEvent},
+ {"KeyDown" ,vtkCommand::KeyPressEvent},
+ {"KeyUp" ,vtkCommand::KeyReleaseEvent},
+ {"StartMouseWheel" ,vtkCommand::NoEvent},
+ {"MouseWheel" ,WheelEvent},
+ {"EndMouseWheel" ,vtkCommand::NoEvent},
+ {"StartPinch" ,vtkCommand::StartPinchEvent},
+ {"Pinch" ,vtkCommand::PinchEvent},
+ {"EndPinch" ,vtkCommand::EndPinchEvent},
+ {"StartPan" ,vtkCommand::StartPanEvent},
+ {"Pan" ,vtkCommand::PanEvent},
+ {"EndPan" ,vtkCommand::EndPanEvent},
+ {"StartRotate" ,vtkCommand::StartRotateEvent},
+ {"Rotate" ,vtkCommand::RotateEvent},
+ {"EndRotate" ,vtkCommand::RenderEvent},
+ {"Button3D" ,vtkCommand::NoEvent},
+ {"Move3D" ,vtkCommand::NoEvent},
+ {"StartPointerLock" ,vtkCommand::NoEvent},
+ {"EndPointerLock" ,vtkCommand::NoEvent},
+ {"StartInteraction" ,vtkCommand::NoEvent},
+ {"Interaction" ,vtkCommand::NoEvent},
+ {"EndInteraction" ,vtkCommand::NoEvent},
+ {"AnimationFrameRateUpdate" ,vtkCommand::NoEvent}
+};
+// clang-format on
+
+vtkSetObjectImplementationMacro(vtkRemoteInteractionAdapter, Interactor, vtkRenderWindowInteractor);
+//----------------------------------------------------------------------------
+vtkStandardNewMacro(vtkRemoteInteractionAdapter);
+
+//----------------------------------------------------------------------------
+vtkRemoteInteractionAdapter::vtkRemoteInteractionAdapter() = default;
+
+//----------------------------------------------------------------------------
+vtkRemoteInteractionAdapter::~vtkRemoteInteractionAdapter()
+{
+ this->SetInteractor(nullptr);
+}
+
+//----------------------------------------------------------------------------
+void vtkRemoteInteractionAdapter::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+//----------------------------------------------------------------------------
+// based on QVTKInteractorAdapter::ProcessEvent(QEvent* e, vtkRenderWindowInteractor* iren)
+bool vtkRemoteInteractionAdapter::ProcessEvent(vtkRenderWindowInteractor* iren,
+ const std::string& event_str, double devicePixelRatio, double devicePixelRatioTolerance)
+{
+
+ if (!iren)
+ {
+ vtkLogF(ERROR, "Null interactor passed");
+ return false;
+ }
+ // the following events only happen if the interactor is enabled
+ if (!iren->GetEnabled())
+ {
+ return false;
+ }
+
+ try
+ {
+ nlohmann::json event = nlohmann::json::parse(event_str);
+ const std::string& type = event.at("type");
+ vtkLogF(TRACE, "event %s", event.dump(1).c_str());
+ const int eventType = EVENT_MAP.at(type);
+ switch (eventType)
+ {
+ case vtkCommand::EnterEvent:
+ case vtkCommand::LeaveEvent:
+ iren->InvokeEvent(eventType, (void*)&event);
+ break;
+ case vtkCommand::MouseMoveEvent:
+ case vtkCommand::LeftButtonPressEvent:
+ case vtkCommand::LeftButtonReleaseEvent:
+ case vtkCommand::RightButtonPressEvent:
+ case vtkCommand::RightButtonReleaseEvent:
+ case vtkCommand::MiddleButtonPressEvent:
+ case vtkCommand::MiddleButtonReleaseEvent:
+ {
+ const int x =
+ (event.at("x").get<double>() / event.at("w").get<double>() * devicePixelRatio +
+ devicePixelRatioTolerance) *
+ iren->GetRenderWindow()->GetSize()[0];
+ const int y =
+ (event.at("y").get<double>() / event.at("h").get<double>() * devicePixelRatio +
+ devicePixelRatioTolerance) *
+ iren->GetRenderWindow()->GetSize()[1];
+
+ const int ctrlKeyPressed = event.at("ctrlKey").get<int>();
+ const int altKeyPressed = event.at("altKey").get<int>();
+ const int shiftKeyPressed = event.at("shiftKey").get<int>();
+ iren->SetEventInformation(x, y, ctrlKeyPressed, shiftKeyPressed);
+ iren->SetAltKey(altKeyPressed);
+ iren->InvokeEvent(eventType, (void*)&event);
+ break;
+ }
+ case vtkCommand::KeyPressEvent:
+ case vtkCommand::KeyReleaseEvent:
+ {
+ const int ctrlKeyPressed = event.at("controlKey").get<int>();
+ const int altKeyPressed = event.at("altKey").get<int>();
+ const int shiftKeyPressed = event.at("shiftKey").get<int>();
+ const char asciiCode = event.at("keyCode").get<int>();
+ const std::string& key = event.at("key");
+ iren->SetKeyEventInformation(ctrlKeyPressed, shiftKeyPressed, asciiCode, 0, key.c_str());
+ iren->SetAltKey(altKeyPressed);
+ iren->InvokeEvent(eventType);
+ if (eventType == vtkCommand::KeyPressEvent && asciiCode != '\0') // TODO check comparson
+ {
+ iren->InvokeEvent(vtkCommand::CharEvent, (void*)&event);
+ }
+ break;
+ }
+ case WheelEvent:
+ {
+ const int x =
+ (event.at("x").get<double>() / event.at("w").get<double>() * devicePixelRatio +
+ devicePixelRatioTolerance) *
+ iren->GetRenderWindow()->GetSize()[0];
+ const int y =
+ (event.at("y").get<double>() / event.at("h").get<double>() * devicePixelRatio +
+ devicePixelRatioTolerance) *
+ iren->GetRenderWindow()->GetSize()[1];
+
+ const int ctrlKeyPressed = event.at("ctrlKey").get<int>();
+ const int altKeyPressed = event.at("altKey").get<int>();
+ const int shiftKeyPressed = event.at("shiftKey").get<int>();
+
+ iren->SetEventInformation(x, y, ctrlKeyPressed, shiftKeyPressed);
+ iren->SetAltKey(altKeyPressed);
+
+ static double accumulatedDelta = 0;
+ const double verticalDelta = event.at("spinY").get<double>();
+ accumulatedDelta += verticalDelta;
+ const double threshold = 1.0; // in vtk-js the value comes normalized
+
+ // invoke vtk event when accumulated delta passes the threshold
+ // Note: in javascript a forward (away from the user MouseWheelEvent is
+ // indicated with a negative value in contrast to Qt.
+ if (accumulatedDelta <= -threshold && verticalDelta != 0.0)
+ {
+ iren->InvokeEvent(vtkCommand::MouseWheelForwardEvent, (void*)&event);
+ accumulatedDelta = 0;
+ }
+ else if (accumulatedDelta >= threshold && verticalDelta != 0.0)
+ {
+ iren->InvokeEvent(vtkCommand::MouseWheelBackwardEvent, (void*)&event);
+ accumulatedDelta = 0;
+ }
+
+ break;
+ }
+ case vtkCommand::StartPinchEvent:
+ case vtkCommand::EndPinchEvent:
+ case vtkCommand::PinchEvent:
+ case vtkCommand::StartPanEvent:
+ case vtkCommand::EndPanEvent:
+ case vtkCommand::PanEvent:
+ case vtkCommand::StartRotateEvent:
+ case vtkCommand::EndRotateEvent:
+ case vtkCommand::RotateEvent:
+ {
+ // Store event information to restore after gesture is completed
+ int eventPosition[2];
+ iren->GetEventPosition(eventPosition);
+ int lastEventPosition[2];
+ iren->GetLastEventPosition(lastEventPosition);
+
+ // get center of positions for event
+ int position[2] = { 0, 0 };
+ for (const auto& item : event.at("positions"))
+ {
+ position[0] += item.at("x").get<double>() / event.at("w").get<double>();
+ position[1] += item.at("y").get<double>() / event.at("h").get<double>();
+ }
+
+ position[0] /= static_cast<int>(event.at("positions").size());
+ position[1] /= static_cast<int>(event.at("positions").size());
+
+ iren->SetEventInformation(position[0] * devicePixelRatio * devicePixelRatioTolerance,
+ position[1] * devicePixelRatio * devicePixelRatioTolerance);
+
+ if (eventType == vtkCommand::StartPinchEvent || eventType == vtkCommand::EndPinchEvent ||
+ eventType == vtkCommand::PinchEvent)
+ {
+ const double factor = event.at("factor").get<double>();
+ iren->SetScale(1.0);
+ iren->SetScale(factor);
+ }
+ else if (eventType == vtkCommand::StartPanEvent || eventType == vtkCommand::EndPanEvent ||
+ eventType == vtkCommand::PanEvent)
+ {
+ double translation[2] = { event.at("translation").at(0).get<double>(),
+ event.at("translation").at(1).get<double>() };
+ iren->SetTranslation(translation);
+ }
+ else if (eventType == vtkCommand::StartRotateEvent ||
+ eventType == vtkCommand::EndRotateEvent || eventType == vtkCommand::RotateEvent)
+ {
+ const double rotation = event.at("rotation").get<double>();
+ iren->SetRotation(rotation);
+ }
+ else
+ {
+ vtkLogF(ERROR, "Unexpected Event Type");
+ return false;
+ }
+ iren->InvokeEvent(eventType, (void*)&event);
+ }
+ break;
+
+ case vtkCommand::InteractionEvent:
+ case vtkCommand::StartInteractionEvent:
+ case vtkCommand::EndInteractionEvent:
+ case vtkCommand::NoEvent:
+ // nothing to do
+ break;
+ default:
+ vtkLogF(WARNING, "Unhandled event: %s", type.c_str());
+ break;
+ }
+ return true;
+ }
+ catch (std::out_of_range& e)
+ {
+ vtkLogF(ERROR, "Skipping Event: Unknown event type \n%s", e.what());
+ return false;
+ }
+ catch (nlohmann::json::out_of_range& e)
+ {
+ vtkLogF(ERROR, "Skipping Event \n%s", e.what());
+ return false;
+ }
+}
+
+//----------------------------------------------------------------------------
+bool vtkRemoteInteractionAdapter::ProcessEvent(const std::string& event_str)
+{
+ return ProcessEvent(
+ this->Interactor, event_str, this->DevicePixelRatio, this->DevicePixelRatioTolerance);
+}
+
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+/**
+ * @class vtkRemoteInteractionAdapter
+ * @brief Map vtk-js interaction events to native VTK events
+ *
+ * Apply an vtk-js events to a vtkRenderWindowInteractor.
+ * For the expected format see
+ * https://github.com/Kitware/vtk-js/blob/master/Sources/Interaction/Style/InteractorStyleRemoteMouse/index.js
+ *
+ * Events are processed in the `ProcessEvent` method which can be called
+ * either as a static method providing all the relevant parameters as arguments
+ * or a class method with the parameters provided via member variables.
+ *
+ */
+
+#ifndef vtkRemoteInteractionAdapter_h
+#define vtkRemoteInteractionAdapter_h
+
+#include "vtkObject.h"
+#include "vtkWebCoreModule.h" // for exports
+
+VTK_ABI_NAMESPACE_BEGIN
+
+class vtkRenderWindowInteractor;
+
+class VTKWEBCORE_EXPORT vtkRemoteInteractionAdapter : public vtkObject
+{
+public:
+ static vtkRemoteInteractionAdapter* New();
+ vtkTypeMacro(vtkRemoteInteractionAdapter, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ /**
+ * @brief Apply the vtk-js event to the internal RenderWindowInteractor
+ * @param event stringified json representation of a vtk-js interaction event.
+ * @return true if the event is processed , false otherwise
+ */
+ bool ProcessEvent(const std::string& event);
+
+ /**
+ * Static version of ProcessEvent(const std::string&)
+ * @return true if the event is processed , false otherwise
+ */
+ static bool ProcessEvent(vtkRenderWindowInteractor* iren, const std::string& event,
+ double devicePixelRatio = 1.0, double devicePixelRatioTolerance = 1e-5);
+
+ ///@{
+ // Get/Set the ratio between physical (onscreen) pixel and logical (rendered image)
+ vtkSetMacro(DevicePixelRatio, double);
+ vtkGetMacro(DevicePixelRatio, double);
+ ///@}
+
+ ///@{
+ /**
+ * Tolerance used when truncating the event position from
+ * physical to logical. i.e. int event_position_x = int(event.at("x") * devicePixelRatio +
+ * devicePixelRatioTolerance)
+ */
+ vtkSetMacro(DevicePixelRatioTolerance, double);
+ vtkGetMacro(DevicePixelRatioTolerance, double);
+ ///@}
+
+ ///@{
+ // Get/Set the Interactor to apply the event to.
+ void SetInteractor(vtkRenderWindowInteractor* iren);
+ vtkGetObjectMacro(Interactor, vtkRenderWindowInteractor);
+ ///@}
+
+protected:
+ vtkRemoteInteractionAdapter();
+ ~vtkRemoteInteractionAdapter() override;
+
+private:
+ vtkRemoteInteractionAdapter(const vtkRemoteInteractionAdapter&) = delete;
+ void operator=(const vtkRemoteInteractionAdapter&) = delete;
+
+ double DevicePixelRatio = 1.0;
+ double DevicePixelRatioTolerance = 1e-5;
+ vtkRenderWindowInteractor* Interactor = nullptr;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkWebApplication.h"
+
+#include "vtkBase64Utilities.h"
+#include "vtkCamera.h"
+#include "vtkCommand.h"
+#include "vtkDataEncoder.h"
+#include "vtkImageData.h"
+#include "vtkJPEGWriter.h"
+#include "vtkNew.h"
+#include "vtkObjectFactory.h"
+#include "vtkObjectIdMap.h"
+#include "vtkPNGWriter.h"
+#include "vtkPointData.h"
+#include "vtkRenderWindow.h"
+#include "vtkRenderWindowInteractor.h"
+#include "vtkRendererCollection.h"
+#include "vtkSmartPointer.h"
+#include "vtkTimerLog.h"
+#include "vtkUnsignedCharArray.h"
+#include "vtkWebGLExporter.h"
+#include "vtkWebGLObject.h"
+#include "vtkWebInteractionEvent.h"
+#include "vtkWindowToImageFilter.h"
+
+#include <cassert>
+#include <cmath>
+#include <map>
+#include <sstream>
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkWebApplication::vtkInternals
+{
+public:
+ struct ImageCacheValueType
+ {
+ public:
+ vtkSmartPointer<vtkUnsignedCharArray> Data;
+ bool NeedsRender;
+ bool HasImagesBeingProcessed;
+ vtkObject* ViewPointer;
+ unsigned long ObserverId;
+ ImageCacheValueType()
+ : NeedsRender(true)
+ , HasImagesBeingProcessed(false)
+ , ViewPointer(nullptr)
+ , ObserverId(0)
+ {
+ }
+
+ void SetListener(vtkObject* view)
+ {
+ if (this->ViewPointer == view)
+ {
+ return;
+ }
+
+ if (this->ViewPointer && this->ObserverId)
+ {
+ this->ViewPointer->RemoveObserver(this->ObserverId);
+ this->ObserverId = 0;
+ }
+ this->ViewPointer = view;
+ if (this->ViewPointer)
+ {
+ this->ObserverId = this->ViewPointer->AddObserver(
+ vtkCommand::AnyEvent, this, &ImageCacheValueType::ViewEventListener);
+ }
+ }
+
+ void RemoveListener(vtkObject* view)
+ {
+ if (this->ViewPointer && this->ViewPointer == view && this->ObserverId)
+ {
+ this->ViewPointer->RemoveObserver(this->ObserverId);
+ this->ObserverId = 0;
+ this->ViewPointer = nullptr;
+ }
+ }
+
+ void ViewEventListener(vtkObject*, unsigned long, void*) { this->NeedsRender = true; }
+ };
+ typedef std::map<void*, ImageCacheValueType> ImageCacheType;
+ ImageCacheType ImageCache;
+
+ typedef std::map<void*, unsigned int> ButtonStatesType;
+ ButtonStatesType ButtonStates;
+
+ vtkNew<vtkDataEncoder> Encoder;
+
+ // WebGL related struct
+ struct WebGLObjCacheValue
+ {
+ public:
+ int ObjIndex;
+ std::map<int, std::string> BinaryParts;
+ };
+ // map for <vtkWebGLExporter, <webgl-objID, WebGLObjCacheValue> >
+ typedef std::map<std::string, WebGLObjCacheValue> WebGLObjId2IndexMap;
+ std::map<vtkWebGLExporter*, WebGLObjId2IndexMap> WebGLExporterObjIdMap;
+ // map for <vtkRenderWindow, vtkWebGLExporter>
+ std::map<vtkRenderWindow*, vtkSmartPointer<vtkWebGLExporter>> ViewWebGLMap;
+ std::string LastAllWebGLBinaryObjects;
+ vtkNew<vtkObjectIdMap> ObjectIdMap;
+};
+
+vtkStandardNewMacro(vtkWebApplication);
+//------------------------------------------------------------------------------
+vtkWebApplication::vtkWebApplication()
+ : ImageEncoding(ENCODING_BASE64)
+ , ImageCompression(COMPRESSION_JPEG)
+ , Internals(new vtkWebApplication::vtkInternals())
+{
+}
+
+//------------------------------------------------------------------------------
+vtkWebApplication::~vtkWebApplication()
+{
+ delete this->Internals;
+ this->Internals = nullptr;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebApplication::SetNumberOfEncoderThreads(vtkTypeUInt32 numThreads)
+{
+ this->Internals->Encoder->SetMaxThreads(numThreads);
+ this->Internals->Encoder->Initialize();
+}
+
+//------------------------------------------------------------------------------
+vtkTypeUInt32 vtkWebApplication::GetNumberOfEncoderThreads()
+{
+ return this->Internals->Encoder->GetMaxThreads();
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebApplication::GetHasImagesBeingProcessed(vtkRenderWindow* view)
+{
+ const vtkInternals::ImageCacheValueType& value = this->Internals->ImageCache[view];
+ return value.HasImagesBeingProcessed;
+}
+
+//------------------------------------------------------------------------------
+vtkUnsignedCharArray* vtkWebApplication::InteractiveRender(vtkRenderWindow* view, int quality)
+{
+ // for now, just do the same as StillRender().
+ return this->StillRender(view, quality);
+}
+
+//------------------------------------------------------------------------------
+void vtkWebApplication::InvalidateCache(vtkRenderWindow* view)
+{
+ this->Internals->ImageCache[view].NeedsRender = true;
+}
+
+//------------------------------------------------------------------------------
+vtkUnsignedCharArray* vtkWebApplication::StillRender(vtkRenderWindow* view, int quality)
+{
+ if (!view)
+ {
+ vtkErrorMacro("No view specified.");
+ return nullptr;
+ }
+
+ auto viewID = this->Internals->ObjectIdMap->GetGlobalId(view);
+ vtkInternals::ImageCacheValueType& value = this->Internals->ImageCache[view];
+ value.SetListener(view);
+
+ if (!value.NeedsRender &&
+ value.Data != nullptr /* FIXME SEB &&
+ view->HasDirtyRepresentation() == false */)
+ {
+ bool latest = this->Internals->Encoder->GetLatestOutput(viewID, value.Data);
+ value.HasImagesBeingProcessed = !latest;
+ return value.Data;
+ }
+
+ // cout << "Regenerating " << endl;
+ // vtkTimerLog::ResetLog();
+ // vtkTimerLog::CleanupLog();
+ // vtkTimerLog::MarkStartEvent("StillRenderToString");
+ // vtkTimerLog::MarkStartEvent("CaptureWindow");
+
+ view->Render();
+
+ // TODO: We should add logic to check if a new rendering needs to be done and
+ // then alone do a new rendering otherwise use the cached image.
+ vtkNew<vtkWindowToImageFilter> w2i;
+ w2i->SetInput(view);
+ w2i->SetScale(1);
+ w2i->ReadFrontBufferOff();
+ w2i->ShouldRerenderOff();
+ w2i->FixBoundaryOn();
+ w2i->Update();
+
+ auto image = vtkSmartPointer<vtkImageData>::New();
+ image->ShallowCopy(w2i->GetOutput());
+
+ // vtkTimerLog::MarkEndEvent("CaptureWindow");
+
+ // vtkTimerLog::MarkEndEvent("StillRenderToString");
+ // vtkTimerLog::DumpLogWithIndents(&cout, 0.0);
+
+ this->Internals->Encoder->Push(viewID, image, quality, this->ImageEncoding);
+
+ if (value.Data == nullptr)
+ {
+ // we need to wait till output is processed.
+ // cout << "Flushing" << endl;
+ this->Internals->Encoder->Flush(viewID);
+ // cout << "Done Flushing" << endl;
+ }
+
+ bool latest = this->Internals->Encoder->GetLatestOutput(viewID, value.Data);
+ value.HasImagesBeingProcessed = !latest;
+ value.NeedsRender = false;
+ return value.Data;
+}
+
+//------------------------------------------------------------------------------
+const char* vtkWebApplication::StillRenderToString(
+ vtkRenderWindow* view, vtkMTimeType time, int quality)
+{
+ vtkUnsignedCharArray* array = this->StillRender(view, quality);
+ if (array && array->GetMTime() != time)
+ {
+ this->LastStillRenderToMTime = array->GetMTime();
+ // cout << "Image size: " << array->GetNumberOfTuples() << endl;
+ return reinterpret_cast<char*>(array->GetPointer(0));
+ }
+ return nullptr;
+}
+
+//------------------------------------------------------------------------------
+vtkUnsignedCharArray* vtkWebApplication::StillRenderToBuffer(
+ vtkRenderWindow* view, vtkMTimeType time, int quality)
+{
+ vtkUnsignedCharArray* array = this->StillRender(view, quality);
+ if (array && array->GetMTime() != time)
+ {
+ this->LastStillRenderToMTime = array->GetMTime();
+ return array;
+ }
+ return nullptr;
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebApplication::HandleInteractionEvent(vtkRenderWindow* view, vtkWebInteractionEvent* event)
+{
+ vtkRenderWindowInteractor* iren = nullptr;
+
+ if (view)
+ {
+ iren = view->GetInteractor();
+ }
+ else
+ {
+ vtkErrorMacro("Interaction not supported for view : " << view);
+ return false;
+ }
+
+ int ctrlKey = (event->GetModifiers() & vtkWebInteractionEvent::CTRL_KEY) != 0 ? 1 : 0;
+ int shiftKey = (event->GetModifiers() & vtkWebInteractionEvent::SHIFT_KEY) != 0 ? 1 : 0;
+
+ // Handle scroll action if any
+ if (event->GetScroll())
+ {
+ iren->SetEventInformation(0, 0, ctrlKey, shiftKey, event->GetKeyCode(), 0);
+ iren->MouseMoveEvent();
+ iren->RightButtonPressEvent();
+ iren->SetEventInformation(
+ 0, event->GetScroll() * 10, ctrlKey, shiftKey, event->GetKeyCode(), 0);
+ iren->MouseMoveEvent();
+ iren->RightButtonReleaseEvent();
+ this->Internals->ImageCache[view].NeedsRender = true;
+ return true;
+ }
+
+ const int* viewSize = view->GetSize();
+ int posX = std::floor(viewSize[0] * event->GetX() + 0.5);
+ int posY = std::floor(viewSize[1] * event->GetY() + 0.5);
+
+ iren->SetEventInformation(
+ posX, posY, ctrlKey, shiftKey, event->GetKeyCode(), event->GetRepeatCount());
+
+ unsigned int prev_buttons = this->Internals->ButtonStates[view];
+ unsigned int changed_buttons = (event->GetButtons() ^ prev_buttons);
+ iren->MouseMoveEvent();
+ if ((changed_buttons & vtkWebInteractionEvent::LEFT_BUTTON) != 0)
+ {
+ if ((event->GetButtons() & vtkWebInteractionEvent::LEFT_BUTTON) != 0)
+ {
+ iren->LeftButtonPressEvent();
+ if (event->GetRepeatCount() > 0)
+ {
+ iren->LeftButtonReleaseEvent();
+ }
+ }
+ else
+ {
+ iren->LeftButtonReleaseEvent();
+ }
+ }
+
+ if ((changed_buttons & vtkWebInteractionEvent::RIGHT_BUTTON) != 0)
+ {
+ if ((event->GetButtons() & vtkWebInteractionEvent::RIGHT_BUTTON) != 0)
+ {
+ iren->RightButtonPressEvent();
+ if (event->GetRepeatCount() > 0)
+ {
+ iren->RightButtonPressEvent();
+ }
+ }
+ else
+ {
+ iren->RightButtonReleaseEvent();
+ }
+ }
+ if ((changed_buttons & vtkWebInteractionEvent::MIDDLE_BUTTON) != 0)
+ {
+ if ((event->GetButtons() & vtkWebInteractionEvent::MIDDLE_BUTTON) != 0)
+ {
+ iren->MiddleButtonPressEvent();
+ if (event->GetRepeatCount() > 0)
+ {
+ iren->MiddleButtonPressEvent();
+ }
+ }
+ else
+ {
+ iren->MiddleButtonReleaseEvent();
+ }
+ }
+
+ this->Internals->ButtonStates[view] = event->GetButtons();
+
+ bool needs_render = (changed_buttons != 0 || event->GetButtons());
+ this->Internals->ImageCache[view].NeedsRender = needs_render;
+ return needs_render;
+}
+
+//------------------------------------------------------------------------------
+const char* vtkWebApplication::GetWebGLSceneMetaData(vtkRenderWindow* view)
+{
+ if (!view)
+ {
+ vtkErrorMacro("No view specified.");
+ return nullptr;
+ }
+
+ // We use the camera focal point to be the center of rotation
+ double centerOfRotation[3];
+ vtkCamera* cam = view->GetRenderers()->GetFirstRenderer()->GetActiveCamera();
+ cam->GetFocalPoint(centerOfRotation);
+
+ if (this->Internals->ViewWebGLMap.find(view) == this->Internals->ViewWebGLMap.end())
+ {
+ this->Internals->ViewWebGLMap[view] = vtkSmartPointer<vtkWebGLExporter>::New();
+ }
+
+ std::stringstream globalIdAsString;
+ globalIdAsString << this->Internals->ObjectIdMap->GetGlobalId(view);
+
+ vtkWebGLExporter* webglExporter = this->Internals->ViewWebGLMap[view];
+ webglExporter->parseScene(view->GetRenderers(), globalIdAsString.str().c_str(), VTK_PARSEALL);
+
+ vtkInternals::WebGLObjId2IndexMap webglMap;
+ for (int i = 0; i < webglExporter->GetNumberOfObjects(); ++i)
+ {
+ vtkWebGLObject* wObj = webglExporter->GetWebGLObject(i);
+ if (wObj && wObj->isVisible())
+ {
+ vtkInternals::WebGLObjCacheValue val;
+ val.ObjIndex = i;
+ for (int j = 0; j < wObj->GetNumberOfParts(); ++j)
+ {
+ val.BinaryParts[j] = "";
+ }
+ webglMap[wObj->GetId()] = val;
+ }
+ }
+ this->Internals->WebGLExporterObjIdMap[webglExporter] = webglMap;
+ webglExporter->SetCenterOfRotation(static_cast<float>(centerOfRotation[0]),
+ static_cast<float>(centerOfRotation[1]), static_cast<float>(centerOfRotation[2]));
+ return webglExporter->GenerateMetadata();
+}
+
+//------------------------------------------------------------------------------
+const char* vtkWebApplication::GetWebGLBinaryData(vtkRenderWindow* view, const char* id, int part)
+{
+ if (!view)
+ {
+ vtkErrorMacro("No view specified.");
+ return nullptr;
+ }
+ if (this->Internals->ViewWebGLMap.find(view) == this->Internals->ViewWebGLMap.end())
+ {
+ if (this->GetWebGLSceneMetaData(view) == nullptr)
+ {
+ vtkErrorMacro("Failed to generate WebGL MetaData for: " << view);
+ return nullptr;
+ }
+ }
+
+ vtkWebGLExporter* webglExporter = this->Internals->ViewWebGLMap[view];
+ if (webglExporter == nullptr)
+ {
+ vtkErrorMacro("There is no cached WebGL Exporter for: " << view);
+ return nullptr;
+ }
+
+ if (!this->Internals->WebGLExporterObjIdMap[webglExporter].empty() &&
+ this->Internals->WebGLExporterObjIdMap[webglExporter].find(id) !=
+ this->Internals->WebGLExporterObjIdMap[webglExporter].end())
+ {
+ vtkInternals::WebGLObjCacheValue* cachedVal =
+ &(this->Internals->WebGLExporterObjIdMap[webglExporter][id]);
+ if (cachedVal->BinaryParts.find(part) != cachedVal->BinaryParts.end())
+ {
+ if (cachedVal->BinaryParts[part].empty())
+ {
+ vtkWebGLObject* obj = webglExporter->GetWebGLObject(cachedVal->ObjIndex);
+ if (obj && obj->isVisible())
+ {
+ // Manage Base64
+ vtkNew<vtkBase64Utilities> base64;
+ unsigned char* output = new unsigned char[obj->GetBinarySize(part) * 2];
+ int size =
+ base64->Encode(obj->GetBinaryData(part), obj->GetBinarySize(part), output, false);
+ cachedVal->BinaryParts[part] = std::string((const char*)output, size);
+ delete[] output;
+ }
+ }
+ return cachedVal->BinaryParts[part].c_str();
+ }
+ }
+
+ return nullptr;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebApplication::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+ os << indent << "ImageEncoding: " << this->ImageEncoding << endl;
+ os << indent << "ImageCompression: " << this->ImageCompression << endl;
+}
+
+//------------------------------------------------------------------------------
+vtkObjectIdMap* vtkWebApplication::GetObjectIdMap()
+{
+ return this->Internals->ObjectIdMap;
+}
+
+//------------------------------------------------------------------------------
+std::string vtkWebApplication::GetObjectId(vtkObject* obj)
+{
+ std::ostringstream oss;
+ oss << std::hex << static_cast<void*>(obj);
+ return oss.str();
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebApplication
+ * @brief defines ParaViewWeb application interface.
+ *
+ * vtkWebApplication defines the core interface for a ParaViewWeb application.
+ * This exposes methods that make it easier to manage views and rendered images
+ * from views.
+ */
+
+#ifndef vtkWebApplication_h
+#define vtkWebApplication_h
+
+#include "vtkObject.h"
+#include "vtkWebCoreModule.h" // needed for exports
+#include <string> // needed for std::string
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkObjectIdMap;
+class vtkRenderWindow;
+class vtkUnsignedCharArray;
+class vtkWebInteractionEvent;
+
+class VTKWEBCORE_EXPORT vtkWebApplication : public vtkObject
+{
+public:
+ static vtkWebApplication* New();
+ vtkTypeMacro(vtkWebApplication, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ ///@{
+ /**
+ * Set the encoding to be used for rendered images.
+ */
+ enum
+ {
+ ENCODING_NONE = 0,
+ ENCODING_BASE64 = 1
+ };
+ vtkSetClampMacro(ImageEncoding, int, ENCODING_NONE, ENCODING_BASE64);
+ vtkGetMacro(ImageEncoding, int);
+ ///@}
+
+ ///@{
+ /**
+ * Set the compression to be used for rendered images.
+ */
+ enum
+ {
+ COMPRESSION_NONE = 0,
+ COMPRESSION_PNG = 1,
+ COMPRESSION_JPEG = 2
+ };
+ vtkSetClampMacro(ImageCompression, int, COMPRESSION_NONE, COMPRESSION_JPEG);
+ vtkGetMacro(ImageCompression, int);
+ ///@}
+
+ ///@{
+ /**
+ * Set the number of worker threads to use for image encoding. Calling this
+ * method with a number greater than 32 or less than zero will have no effect.
+ */
+ void SetNumberOfEncoderThreads(vtkTypeUInt32);
+ vtkTypeUInt32 GetNumberOfEncoderThreads();
+ ///@}
+
+ ///@{
+ /**
+ * Render a view and obtain the rendered image.
+ */
+ vtkUnsignedCharArray* StillRender(vtkRenderWindow* view, int quality = 100);
+ vtkUnsignedCharArray* InteractiveRender(vtkRenderWindow* view, int quality = 50);
+ const char* StillRenderToString(vtkRenderWindow* view, vtkMTimeType time = 0, int quality = 100);
+ vtkUnsignedCharArray* StillRenderToBuffer(
+ vtkRenderWindow* view, vtkMTimeType time = 0, int quality = 100);
+ ///@}
+
+ /**
+ * StillRenderToString() need not necessary returns the most recently rendered
+ * image. Use this method to get whether there are any pending images being
+ * processed concurrently.
+ */
+ bool GetHasImagesBeingProcessed(vtkRenderWindow*);
+
+ /**
+ * Communicate mouse interaction to a view.
+ * Returns true if the interaction changed the view state, otherwise returns false.
+ */
+ bool HandleInteractionEvent(vtkRenderWindow* view, vtkWebInteractionEvent* event);
+
+ /**
+ * Invalidate view cache
+ */
+ void InvalidateCache(vtkRenderWindow* view);
+
+ ///@{
+ /**
+ * Return the MTime of the last array exported by StillRenderToString.
+ */
+ vtkGetMacro(LastStillRenderToMTime, vtkMTimeType);
+ ///@}
+
+ /**
+ * Return the Meta data description of the input scene in JSON format.
+ * This is using the vtkWebGLExporter to parse the scene.
+ * NOTE: This should be called before getting the webGL binary data.
+ */
+ const char* GetWebGLSceneMetaData(vtkRenderWindow* view);
+
+ /**
+ * Return the binary data given the part index
+ * and the webGL object piece id in the scene.
+ */
+ const char* GetWebGLBinaryData(vtkRenderWindow* view, const char* id, int partIndex);
+
+ vtkObjectIdMap* GetObjectIdMap();
+
+ /**
+ * Return a hexadecimal formatted string of the VTK object's memory address,
+ * useful for uniquely identifying the object when exporting data.
+ *
+ * e.g. 0x8f05a90
+ */
+ static std::string GetObjectId(vtkObject* obj);
+
+protected:
+ vtkWebApplication();
+ ~vtkWebApplication() override;
+
+ int ImageEncoding;
+ int ImageCompression;
+ vtkMTimeType LastStillRenderToMTime;
+
+private:
+ vtkWebApplication(const vtkWebApplication&) = delete;
+ void operator=(const vtkWebApplication&) = delete;
+
+ class vtkInternals;
+ vtkInternals* Internals;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkWebInteractionEvent.h"
+
+#include "vtkObjectFactory.h"
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkWebInteractionEvent);
+//------------------------------------------------------------------------------
+vtkWebInteractionEvent::vtkWebInteractionEvent()
+ : Buttons(0)
+ , Modifiers(0)
+ , KeyCode(0)
+ , X(0.0)
+ , Y(0.0)
+ , Scroll(0.0)
+ , RepeatCount(0)
+{
+}
+
+//------------------------------------------------------------------------------
+vtkWebInteractionEvent::~vtkWebInteractionEvent() = default;
+
+//------------------------------------------------------------------------------
+void vtkWebInteractionEvent::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+ os << indent << "Buttons: " << this->Buttons << endl;
+ os << indent << "Modifiers: " << this->Modifiers << endl;
+ os << indent << "KeyCode: " << static_cast<int>(this->KeyCode) << endl;
+ os << indent << "X: " << this->X << endl;
+ os << indent << "Y: " << this->Y << endl;
+ os << indent << "RepeatCount: " << this->RepeatCount << endl;
+ os << indent << "Scroll: " << this->Scroll << endl;
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebInteractionEvent
+ *
+ *
+ */
+
+#ifndef vtkWebInteractionEvent_h
+#define vtkWebInteractionEvent_h
+
+#include "vtkObject.h"
+#include "vtkWebCoreModule.h" // needed for exports
+
+VTK_ABI_NAMESPACE_BEGIN
+class VTKWEBCORE_EXPORT vtkWebInteractionEvent : public vtkObject
+{
+public:
+ static vtkWebInteractionEvent* New();
+ vtkTypeMacro(vtkWebInteractionEvent, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ enum MouseButton
+ {
+ LEFT_BUTTON = 0x01,
+ MIDDLE_BUTTON = 0x02,
+ RIGHT_BUTTON = 0x04
+ };
+
+ enum ModifierKeys
+ {
+ SHIFT_KEY = 0x01,
+ CTRL_KEY = 0x02,
+ ALT_KEY = 0x04,
+ META_KEY = 0x08
+ };
+
+ ///@{
+ /**
+ * Set/Get the mouse buttons state.
+ */
+ vtkSetMacro(Buttons, unsigned int);
+ vtkGetMacro(Buttons, unsigned int);
+ ///@}
+
+ ///@{
+ /**
+ * Set/Get modifier state.
+ */
+ vtkSetMacro(Modifiers, unsigned int);
+ vtkGetMacro(Modifiers, unsigned int);
+ ///@}
+
+ ///@{
+ /**
+ * Set/Get the chart code.
+ */
+ vtkSetMacro(KeyCode, char);
+ vtkGetMacro(KeyCode, char);
+ ///@}
+
+ ///@{
+ /**
+ * Set/Get event position.
+ */
+ vtkSetMacro(X, double);
+ vtkGetMacro(X, double);
+ vtkSetMacro(Y, double);
+ vtkGetMacro(Y, double);
+ vtkSetMacro(Scroll, double);
+ vtkGetMacro(Scroll, double);
+ ///@}
+
+ // Handle double click
+ vtkSetMacro(RepeatCount, int);
+ vtkGetMacro(RepeatCount, int);
+
+protected:
+ vtkWebInteractionEvent();
+ ~vtkWebInteractionEvent() override;
+
+ unsigned int Buttons;
+ unsigned int Modifiers;
+ char KeyCode;
+ double X;
+ double Y;
+ double Scroll;
+ int RepeatCount;
+
+private:
+ vtkWebInteractionEvent(const vtkWebInteractionEvent&) = delete;
+ void operator=(const vtkWebInteractionEvent&) = delete;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkWebUtilities.h"
+#include "vtkPython.h" // Need to be first and used for Py_xxx macros
+
+#include "vtkDataSet.h"
+#include "vtkDataSetAttributes.h"
+#include "vtkJavaScriptDataWriter.h"
+#include "vtkMultiProcessController.h"
+#include "vtkNew.h"
+#include "vtkObjectFactory.h"
+#include "vtkSplitColumnComponents.h"
+#include "vtkTable.h"
+
+#include <sstream>
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkWebUtilities);
+//------------------------------------------------------------------------------
+vtkWebUtilities::vtkWebUtilities() = default;
+
+//------------------------------------------------------------------------------
+vtkWebUtilities::~vtkWebUtilities() = default;
+
+//------------------------------------------------------------------------------
+std::string vtkWebUtilities::WriteAttributesToJavaScript(int field_type, vtkDataSet* dataset)
+{
+ if (dataset == nullptr ||
+ (field_type != vtkDataObject::POINT && field_type != vtkDataObject::CELL))
+ {
+ return "[]";
+ }
+
+ std::ostringstream stream;
+
+ vtkNew<vtkDataSetAttributes> clone;
+ clone->PassData(dataset->GetAttributes(field_type));
+ clone->RemoveArray("vtkValidPointMask");
+
+ vtkNew<vtkTable> table;
+ table->SetRowData(clone);
+
+ vtkNew<vtkSplitColumnComponents> splitter;
+ splitter->SetInputDataObject(table);
+ splitter->Update();
+
+ vtkNew<vtkJavaScriptDataWriter> writer;
+ writer->SetOutputStream(&stream);
+ writer->SetInputDataObject(splitter->GetOutputDataObject(0));
+ writer->SetVariableName(nullptr);
+ writer->SetIncludeFieldNames(false);
+ writer->Write();
+
+ return stream.str();
+}
+
+//------------------------------------------------------------------------------
+std::string vtkWebUtilities::WriteAttributeHeadersToJavaScript(int field_type, vtkDataSet* dataset)
+{
+ if (dataset == nullptr ||
+ (field_type != vtkDataObject::POINT && field_type != vtkDataObject::CELL))
+ {
+ return "[]";
+ }
+
+ std::ostringstream stream;
+ stream << "[";
+
+ vtkDataSetAttributes* dsa = dataset->GetAttributes(field_type);
+ vtkNew<vtkDataSetAttributes> clone;
+ clone->CopyAllocate(dsa, 0);
+ clone->RemoveArray("vtkValidPointMask");
+
+ vtkNew<vtkTable> table;
+ table->SetRowData(clone);
+
+ vtkNew<vtkSplitColumnComponents> splitter;
+ splitter->SetInputDataObject(table);
+ splitter->Update();
+
+ dsa = vtkTable::SafeDownCast(splitter->GetOutputDataObject(0))->GetRowData();
+
+ for (int cc = 0; cc < dsa->GetNumberOfArrays(); cc++)
+ {
+ const char* name = dsa->GetArrayName(cc);
+ if (cc != 0)
+ {
+ stream << ", ";
+ }
+ stream << "\"" << (name ? name : "") << "\"";
+ }
+ stream << "]";
+ return stream.str();
+}
+
+//------------------------------------------------------------------------------
+void vtkWebUtilities::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+//------------------------------------------------------------------------------
+void vtkWebUtilities::ProcessRMIs()
+{
+ vtkWebUtilities::ProcessRMIs(1, 0);
+}
+
+//------------------------------------------------------------------------------
+void vtkWebUtilities::ProcessRMIs(int reportError, int dont_loop)
+{
+ Py_BEGIN_ALLOW_THREADS
+
+ vtkMultiProcessController::GetGlobalController()
+ ->ProcessRMIs(reportError, dont_loop);
+
+ Py_END_ALLOW_THREADS
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebUtilities
+ * @brief collection of utility functions for ParaView Web.
+ *
+ * vtkWebUtilities consolidates miscellaneous utility functions useful for
+ * Python scripts designed for ParaView Web.
+ */
+
+#ifndef vtkWebUtilities_h
+#define vtkWebUtilities_h
+
+#include "vtkObject.h"
+#include "vtkWebCoreModule.h" // needed for exports
+#include <string> // for std::string
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkDataSet;
+
+class VTKWEBCORE_EXPORT vtkWebUtilities : public vtkObject
+{
+public:
+ static vtkWebUtilities* New();
+ vtkTypeMacro(vtkWebUtilities, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ static std::string WriteAttributesToJavaScript(int field_type, vtkDataSet*);
+ static std::string WriteAttributeHeadersToJavaScript(int field_type, vtkDataSet*);
+
+ ///@{
+ /**
+ * This method is similar to the ProcessRMIs() method on the GlobalController
+ * except that it is Python friendly in the sense that it will release the
+ * Python GIS lock, so when run in a thread, this will truly work in the
+ * background without locking the main one.
+ */
+ static void ProcessRMIs();
+ static void ProcessRMIs(int reportError, int dont_loop = 0);
+ ///@}
+
+protected:
+ vtkWebUtilities();
+ ~vtkWebUtilities() override;
+
+private:
+ vtkWebUtilities(const vtkWebUtilities&) = delete;
+ void operator=(const vtkWebUtilities&) = delete;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+set(files
+ vtkmodules/web/__init__.py
+ vtkmodules/web/camera.py
+ vtkmodules/web/dataset_builder.py
+ vtkmodules/web/errors.py
+ vtkmodules/web/protocols.py
+ vtkmodules/web/query_data_model.py
+ vtkmodules/web/render_window_serializer.py
+ vtkmodules/web/testing.py
+ vtkmodules/web/vtkjs_helper.py
+ vtkmodules/web/venv.py
+ vtkmodules/web/wslink.py
+ vtkmodules/web/utils.py)
+
+vtk_module_add_python_package(VTK::WebPython
+ FILES ${files}
+ PACKAGE "vtkmodules.web"
+ MODULE_DESTINATION "${VTK_PYTHON_SITE_PACKAGES_SUFFIX}")
+
+vtk_module_add_python_module(VTK::WebPython
+ PACKAGES "vtkmodules.web")
+
+set_property(GLOBAL APPEND
+ PROPERTY
+ vtk_web_python_modules "wslink>=1.0.4")
--- /dev/null
+if (VTK_WRAP_PYTHON)
+ add_subdirectory(Python)
+endif ()
--- /dev/null
+vtk_add_test_python(
+ NO_DATA NO_VALID NO_OUTPUT
+ TestSerializeRenderWindow.py
+ )
--- /dev/null
+import json
+from vtkmodules.vtkFiltersSources import vtkConeSource
+from vtkmodules.vtkRenderingCore import (
+ vtkActor,
+ vtkPolyDataMapper,
+ vtkRenderWindow,
+ vtkRenderer,
+)
+import vtkmodules.vtkRenderingFreeType
+import vtkmodules.vtkRenderingOpenGL2
+from vtkmodules.web import render_window_serializer as rws
+from vtkmodules.test import Testing
+
+class TestSerializeRenderWindow(Testing.vtkTest):
+ def testSerializeRenderWindow(self):
+ cone = vtkConeSource()
+
+ coneMapper = vtkPolyDataMapper()
+ coneMapper.SetInputConnection(cone.GetOutputPort())
+
+ coneActor = vtkActor()
+ coneActor.SetMapper(coneMapper)
+
+ ren = vtkRenderer()
+ ren.AddActor(coneActor)
+ renWin = vtkRenderWindow()
+ renWin.AddRenderer(ren)
+
+ ren.ResetCamera()
+ renWin.Render()
+
+ # Exercise some of the serialization functionality and make sure it
+ # does not generate a stack trace
+ context = rws.SynchronizationContext()
+ rws.initializeSerializers()
+ jsonData = rws.serializeInstance(None, renWin, rws.getReferenceId(renWin), context, 0)
+
+ # jsonStr = json.dumps(jsonData)
+ # print jsonStr
+ # print len(jsonStr)
+
+if __name__ == "__main__":
+ Testing.main([(TestSerializeRenderWindow, 'test')])
--- /dev/null
+NAME
+ VTK::WebPython
+LIBRARY_NAME
+ vtkWebPython
+CONDITION
+ VTK_WRAP_PYTHON
+GROUPS
+ Web
+SPDX_LICENSE_IDENTIFIER
+ BSD-3-Clause
+SPDX_COPYRIGHT_TEXT
+ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+DEPENDS
+ VTK::CommonCore
+PRIVATE_DEPENDS
+ VTK::FiltersGeometry
+ VTK::WebCore
+TEST_DEPENDS
+ VTK::TestingCore
+TEST_LABELS
+ VTK::Web
+EXCLUDE_WRAP
--- /dev/null
+import hashlib, base64
+
+arrayTypesMapping = [
+ " ", # VTK_VOID 0
+ " ", # VTK_BIT 1
+ "b", # VTK_CHAR 2
+ "B", # VTK_UNSIGNED_CHAR 3
+ "h", # VTK_SHORT 4
+ "H", # VTK_UNSIGNED_SHORT 5
+ "i", # VTK_INT 6
+ "I", # VTK_UNSIGNED_INT 7
+ "l", # VTK_LONG 8
+ "L", # VTK_UNSIGNED_LONG 9
+ "f", # VTK_FLOAT 10
+ "d", # VTK_DOUBLE 11
+ "L", # VTK_ID_TYPE 12
+ " ", # unspecified 13
+ " ", # unspecified 14
+ "b", # signed_char 15
+]
+
+javascriptMapping = {
+ "b": "Int8Array",
+ "B": "Uint8Array",
+ "h": "Int16Array",
+ "H": "Int16Array",
+ "i": "Int32Array",
+ "I": "Uint32Array",
+ "l": "Int32Array",
+ "L": "Uint32Array",
+ "f": "Float32Array",
+ "d": "Float64Array",
+}
+
+
+def iteritems(d, **kwargs):
+ return iter(d.items(**kwargs))
+
+
+def base64Encode(x):
+ return base64.b64encode(x).decode("utf-8")
+
+
+def hashDataArray(dataArray):
+ hashedBit = hashlib.md5(memoryview(dataArray)).hexdigest()
+ typeCode = arrayTypesMapping[dataArray.GetDataType()]
+ return "%s_%d%s" % (hashedBit, dataArray.GetSize(), typeCode)
+
+
+def getJSArrayType(dataArray):
+ return javascriptMapping[arrayTypesMapping[dataArray.GetDataType()]]
+
+
+def getReferenceId(ref):
+ if ref:
+ try:
+ return ref.__this__[1:17]
+ except:
+ idStr = str(ref)[-12:-1]
+ # print('====> fallback ID %s for %s' % (idStr, ref))
+ return idStr
+ return "0x0"
--- /dev/null
+from math import *
+
+# -----------------------------------------------------------------------------
+# Set of helper functions
+# -----------------------------------------------------------------------------
+
+
+def normalize(vect, tolerance=0.00001):
+ mag2 = sum(n * n for n in vect)
+ if abs(mag2 - 1.0) > tolerance:
+ mag = sqrt(mag2)
+ vect = tuple(n / mag for n in vect)
+ return vect
+
+
+def q_mult(q1, q2):
+ w1, x1, y1, z1 = q1
+ w2, x2, y2, z2 = q2
+ w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
+ x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
+ y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2
+ z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2
+ return w, x, y, z
+
+
+def q_conjugate(q):
+ w, x, y, z = q
+ return (w, -x, -y, -z)
+
+
+def qv_mult(q1, v1):
+ q2 = (0.0,) + v1
+ return q_mult(q_mult(q1, q2), q_conjugate(q1))[1:]
+
+
+def axisangle_to_q(v, theta):
+ v = normalize(v)
+ x, y, z = v
+ theta /= 2
+ w = cos(theta)
+ x = x * sin(theta)
+ y = y * sin(theta)
+ z = z * sin(theta)
+ return w, x, y, z
+
+
+def vectProduct(axisA, axisB):
+ xa, ya, za = axisA
+ xb, yb, zb = axisB
+ normalVect = (ya * zb - za * yb, za * xb - xa * zb, xa * yb - ya * xb)
+ normalVect = normalize(normalVect)
+ return normalVect
+
+
+def dotProduct(vecA, vecB):
+ return (vecA[0] * vecB[0]) + (vecA[1] * vecB[1]) + (vecA[2] * vecB[2])
+
+
+def rotate(axis, angle, center, point):
+ angleInRad = 3.141592654 * angle / 180.0
+ rotation = axisangle_to_q(axis, angleInRad)
+ tPoint = tuple((point[i] - center[i]) for i in range(3))
+ rtPoint = qv_mult(rotation, tPoint)
+ rPoint = tuple((rtPoint[i] + center[i]) for i in range(3))
+ return rPoint
+
+
+# -----------------------------------------------------------------------------
+# Spherical Camera
+# -----------------------------------------------------------------------------
+
+
+class SphericalCamera(object):
+ def __init__(
+ self, dataHandler, focalPoint, position, phiAxis, phiAngles, thetaAngles
+ ):
+ self.dataHandler = dataHandler
+ self.cameraSettings = []
+ self.thetaBind = {
+ "mouse": {
+ "drag": {"modifier": 0, "coordinate": 1, "step": 30, "orientation": 1}
+ }
+ }
+ self.phiBind = {
+ "mouse": {
+ "drag": {"modifier": 0, "coordinate": 0, "step": 30, "orientation": 1}
+ }
+ }
+
+ # Convert to serializable type
+ fp = tuple(i for i in focalPoint)
+
+ # Register arguments to the data handler
+ if len(phiAngles) > 1 and phiAngles[-1] + phiAngles[1] == 360:
+ self.dataHandler.registerArgument(
+ priority=0,
+ name="phi",
+ values=phiAngles,
+ ui="slider",
+ loop="modulo",
+ bind=self.phiBind,
+ )
+ else:
+ self.dataHandler.registerArgument(
+ priority=0, name="phi", values=phiAngles, ui="slider", bind=self.phiBind
+ )
+ if thetaAngles[0] < 0 and thetaAngles[0] >= -90:
+ idx = 0
+ for theta in thetaAngles:
+ if theta < 0:
+ idx += 1
+
+ self.dataHandler.registerArgument(
+ priority=0,
+ name="theta",
+ values=[(x + 90) for x in thetaAngles],
+ ui="slider",
+ default=idx,
+ bind=self.thetaBind,
+ )
+ else:
+ self.dataHandler.registerArgument(
+ priority=0,
+ name="theta",
+ values=thetaAngles,
+ ui="slider",
+ bind=self.thetaBind,
+ )
+
+ # Compute all camera settings
+ for theta in thetaAngles:
+ for phi in phiAngles:
+ phiPos = rotate(phiAxis, -phi, fp, position)
+ thetaAxis = vectProduct(
+ phiAxis, tuple(fp[i] - phiPos[i] for i in range(3))
+ )
+ thetaPhiPos = rotate(thetaAxis, theta, fp, phiPos)
+ viewUp = rotate(thetaAxis, theta, (0, 0, 0), phiAxis)
+
+ self.cameraSettings.append(
+ {
+ "theta": theta,
+ "thetaIdx": thetaAngles.index(theta),
+ "phi": phi,
+ "phiIdx": phiAngles.index(phi),
+ "focalPoint": fp,
+ "position": thetaPhiPos,
+ "viewUp": viewUp,
+ }
+ )
+
+ self.dataHandler.updateBasePattern()
+
+ def updatePriority(self, priorityList):
+ keyList = ["theta", "phi"]
+ for idx in range(min(len(priorityList), len(keyList))):
+ self.dataHandler.updatePriority(keyList[idx], priorityList[idx])
+
+ def __iter__(self):
+ for cameraData in self.cameraSettings:
+ self.dataHandler.setArguments(
+ phi=cameraData["phiIdx"], theta=cameraData["thetaIdx"]
+ )
+ yield cameraData
+
+
+# -----------------------------------------------------------------------------
+# Cylindrical Camera
+# -----------------------------------------------------------------------------
+
+
+class CylindricalCamera(object):
+ def __init__(
+ self,
+ dataHandler,
+ focalPoint,
+ position,
+ rotationAxis,
+ phiAngles,
+ translationValues,
+ ):
+ self.dataHandler = dataHandler
+ self.cameraSettings = []
+
+ # Register arguments to the data handler
+ self.dataHandler.registerArgument(
+ priority=0, name="phi", values=phiAngles, ui="slider", loop="modulo"
+ )
+ self.dataHandler.registerArgument(
+ priority=0, name="n_pos", values=translationValues, ui="slider"
+ )
+
+ # Compute all camera settings
+ for translation in translationValues:
+ for phi in phiAngles:
+ phiPos = rotate(rotationAxis, phi, focalPoint, position)
+ newfocalPoint = tuple(
+ focalPoint[i] + (translation * rotationAxis[i]) for i in range(3)
+ )
+ transPhiPoint = tuple(
+ phiPos[i] + (translation * rotationAxis[i]) for i in range(3)
+ )
+
+ self.cameraSettings.append(
+ {
+ "n_pos": translation,
+ "n_posIdx": translationValues.index(translation),
+ "phi": phi,
+ "phiIdx": phiAngles.index(phi),
+ "focalPoint": newfocalPoint,
+ "position": transPhiPoint,
+ "viewUp": rotationAxis,
+ }
+ )
+
+ self.dataHandler.updateBasePattern()
+
+ def updatePriority(self, priorityList):
+ keyList = ["n_pos", "phi"]
+ for idx in range(min(len(priorityList), len(keyList))):
+ self.dataHandler.updatePriority(keyList[idx], priorityList[idx])
+
+ def __iter__(self):
+ for cameraData in self.cameraSettings:
+ self.dataHandler.setArguments(
+ phi=cameraData["phiIdx"], n_pos=cameraData["n_posIdx"]
+ )
+ yield cameraData
+
+
+# -----------------------------------------------------------------------------
+# MultiView Cube Camera
+# -----------------------------------------------------------------------------
+
+
+class CubeCamera(object):
+
+ # positions = [ { position: [x,y,z], args: { i: 1, j: 0, k: 7 } }, ... ]
+ def __init__(self, dataHandler, viewForward, viewUp, positions):
+ self.dataHandler = dataHandler
+ self.cameraSettings = []
+ self.viewForward = viewForward
+ self.viewUp = viewUp
+ self.rightDirection = vectProduct(viewForward, viewUp)
+ self.positions = positions
+
+ # Register arguments to the data handler
+ self.dataHandler.registerArgument(
+ priority=0, name="orientation", values=["f", "b", "r", "l", "u", "d"]
+ )
+
+ # Register arguments to id position
+ self.args = {}
+ for pos in positions:
+ for key in pos["args"]:
+ if key not in self.args:
+ self.args[key] = {}
+ self.args[key][pos["args"][key]] = True
+
+ for key in self.args:
+ self.args[key] = sorted(self.args[key], key=lambda k: int(k))
+
+ self.keyList = self.args.keys()
+ for key in self.args:
+ self.dataHandler.registerArgument(
+ priority=1, name=key, values=self.args[key]
+ )
+
+ self.dataHandler.updateBasePattern()
+
+ def updatePriority(self, priorityList):
+ keyList = ["orientation"]
+ for idx in range(min(len(priorityList), len(keyList))):
+ self.dataHandler.updatePriority(keyList[idx], priorityList[idx])
+
+ def __iter__(self):
+ for pos in self.positions:
+ cameraData = {
+ "position": pos["position"],
+ }
+
+ print("=" * 80)
+ for key in pos["args"]:
+ idx = self.args[key].index(pos["args"][key])
+ self.dataHandler.setArguments(**{key: idx})
+ print(key, idx)
+
+ print("position", cameraData["position"])
+
+ # front
+ cameraData["focalPoint"] = [
+ (cameraData["position"][i] + self.viewForward[i]) for i in range(3)
+ ]
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["orientation"] = "front"
+ self.dataHandler.setArguments(orientation=0)
+ yield cameraData
+
+ # back
+ cameraData["focalPoint"] = [
+ (cameraData["position"][i] - self.viewForward[i]) for i in range(3)
+ ]
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["orientation"] = "back"
+ self.dataHandler.setArguments(orientation=1)
+ yield cameraData
+
+ # right
+ self.dataHandler.setArguments(orientation=2)
+ cameraData["focalPoint"] = [
+ (cameraData["position"][i] + self.rightDirection[i]) for i in range(3)
+ ]
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["orientation"] = "right"
+ yield cameraData
+
+ # left
+ self.dataHandler.setArguments(orientation=3)
+ cameraData["focalPoint"] = [
+ (cameraData["position"][i] - self.rightDirection[i]) for i in range(3)
+ ]
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["orientation"] = "left"
+ yield cameraData
+
+ # up
+ self.dataHandler.setArguments(orientation=4)
+ cameraData["focalPoint"] = [
+ (cameraData["position"][i] + self.viewUp[i]) for i in range(3)
+ ]
+ cameraData["viewUp"] = [(-self.viewForward[i]) for i in range(3)]
+ cameraData["orientation"] = "up"
+ yield cameraData
+
+ # doww
+ self.dataHandler.setArguments(orientation=5)
+ cameraData["focalPoint"] = [
+ (cameraData["position"][i] - self.viewUp[i]) for i in range(3)
+ ]
+ cameraData["viewUp"] = [self.viewForward[i] for i in range(3)]
+ cameraData["orientation"] = "down"
+ yield cameraData
+
+
+# -----------------------------------------------------------------------------
+# MultiView Cube Camera
+# -----------------------------------------------------------------------------
+
+
+class StereoCubeCamera(object):
+
+ # positions = [ { position: [x,y,z], args: { i: 1, j: 0, k: 7 } }, ... ]
+ def __init__(self, dataHandler, viewForward, viewUp, positions, eyeSpacing):
+ self.dataHandler = dataHandler
+ self.cameraSettings = []
+ self.viewForward = viewForward
+ self.viewUp = viewUp
+ self.rightDirection = vectProduct(viewForward, viewUp)
+ self.positions = positions
+ self.eyeSpacing = eyeSpacing
+
+ # Register arguments to the data handler
+ self.dataHandler.registerArgument(
+ priority=0, name="orientation", values=["f", "b", "r", "l", "u", "d"]
+ )
+ self.dataHandler.registerArgument(
+ priority=0, name="eye", values=["left", "right"]
+ )
+
+ # Register arguments to id position
+ self.args = {}
+ for pos in positions:
+ for key in pos["args"]:
+ if key not in self.args:
+ self.args[key] = {}
+ self.args[key][pos["args"][key]] = True
+
+ for key in self.args:
+ self.args[key] = sorted(self.args[key], key=lambda k: int(k))
+
+ self.keyList = self.args.keys()
+ for key in self.args:
+ self.dataHandler.registerArgument(
+ priority=1, name=key, values=self.args[key]
+ )
+
+ self.dataHandler.updateBasePattern()
+
+ def updatePriority(self, priorityList):
+ keyList = ["orientation"]
+ for idx in range(min(len(priorityList), len(keyList))):
+ self.dataHandler.updatePriority(keyList[idx], priorityList[idx])
+
+ def __iter__(self):
+ for pos in self.positions:
+ cameraData = {}
+
+ for key in pos["args"]:
+ idx = self.args[key].index(pos["args"][key])
+ self.dataHandler.setArguments(**{key: idx})
+
+ # front
+ cameraData["orientation"] = "front"
+ self.dataHandler.setArguments(orientation=0)
+ deltaVect = [
+ (v * float(self.eyeSpacing) * 0.5) for v in self.rightDirection
+ ]
+ ## Left-Eye
+ self.dataHandler.setArguments(eye=0)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] - deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] + self.viewForward[i] - deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+ ## Right-Eye
+ self.dataHandler.setArguments(eye=1)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] + deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] + self.viewForward[i] + deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+
+ # back
+ cameraData["orientation"] = "back"
+ self.dataHandler.setArguments(orientation=1)
+ deltaVect = [
+ -(v * float(self.eyeSpacing) * 0.5) for v in self.rightDirection
+ ]
+ ## Left-Eye
+ self.dataHandler.setArguments(eye=0)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] - deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] - self.viewForward[i] - deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+ ## Right-Eye
+ self.dataHandler.setArguments(eye=1)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] + deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] - self.viewForward[i] + deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+
+ # right
+ self.dataHandler.setArguments(orientation=2)
+ cameraData["orientation"] = "right"
+ deltaVect = [-(v * float(self.eyeSpacing) * 0.5) for v in self.viewForward]
+ ## Left-Eye
+ self.dataHandler.setArguments(eye=0)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] - deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] + self.rightDirection[i] - deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+ ## Right-Eye
+ self.dataHandler.setArguments(eye=1)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] + deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] + self.rightDirection[i] + deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+
+ # left
+ self.dataHandler.setArguments(orientation=3)
+ cameraData["orientation"] = "left"
+ deltaVect = [(v * float(self.eyeSpacing) * 0.5) for v in self.viewForward]
+ ## Left-Eye
+ self.dataHandler.setArguments(eye=0)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] - deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] - self.rightDirection[i] - deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+ ## Right-Eye
+ self.dataHandler.setArguments(eye=1)
+ cameraData["viewUp"] = [self.viewUp[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] + deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] - self.rightDirection[i] + deltaVect[i])
+ for i in range(3)
+ ]
+ yield cameraData
+
+ # up
+ self.dataHandler.setArguments(orientation=4)
+ cameraData["orientation"] = "up"
+ deltaVect = [
+ (v * float(self.eyeSpacing) * 0.5) for v in self.rightDirection
+ ]
+ ## Left-Eye
+ self.dataHandler.setArguments(eye=0)
+ cameraData["viewUp"] = [(-self.viewForward[i]) for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] - deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] + self.viewUp[i] - deltaVect[i]) for i in range(3)
+ ]
+ yield cameraData
+ ## Right-Eye
+ self.dataHandler.setArguments(eye=1)
+ cameraData["viewUp"] = [(-self.viewForward[i]) for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] + deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] + self.viewUp[i] + deltaVect[i]) for i in range(3)
+ ]
+ yield cameraData
+
+ # doww
+ self.dataHandler.setArguments(orientation=5)
+ cameraData["orientation"] = "down"
+ deltaVect = [
+ (v * float(self.eyeSpacing) * 0.5) for v in self.rightDirection
+ ]
+ ## Left-Eye
+ self.dataHandler.setArguments(eye=0)
+ cameraData["viewUp"] = [self.viewForward[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] - deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] - self.viewUp[i] - deltaVect[i]) for i in range(3)
+ ]
+ yield cameraData
+ ## Right-Eye
+ self.dataHandler.setArguments(eye=1)
+ cameraData["viewUp"] = [self.viewForward[i] for i in range(3)]
+ cameraData["position"] = [
+ (pos["position"][idx] + deltaVect[idx]) for idx in range(3)
+ ]
+ cameraData["focalPoint"] = [
+ (pos["position"][i] - self.viewUp[i] + deltaVect[i]) for i in range(3)
+ ]
+ yield cameraData
+
+
+# -----------------------------------------------------------------------------
+# MultiView Camera
+# -----------------------------------------------------------------------------
+
+
+class MultiViewCamera(object):
+ def __init__(self, dataHandler):
+ self.dataHandler = dataHandler
+ self.cameraSettings = []
+ self.positionNames = []
+
+ def registerViewPoint(self, name, focalPoint, position, viewUp):
+ self.cameraSettings.append(
+ {
+ "name": name,
+ "nameIdx": len(self.positionNames),
+ "focalPoint": focalPoint,
+ "position": position,
+ "viewUp": viewUp,
+ }
+ )
+ self.positionNames.append(name)
+ self.dataHandler.registerArgument(
+ priority=0, name="multiView", values=self.positionNames
+ )
+ self.dataHandler.updateBasePattern()
+
+ def updatePriority(self, priorityList):
+ keyList = ["multiView"]
+ for idx in range(min(len(priorityList), len(keyList))):
+ self.dataHandler.updatePriority(keyList[idx], priorityList[idx])
+
+ def __iter__(self):
+ for cameraData in self.cameraSettings:
+ self.dataHandler.setArguments(multiView=cameraData["nameIdx"])
+ yield cameraData
+
+
+# -----------------------------------------------------------------------------
+# Helper methods
+# -----------------------------------------------------------------------------
+
+
+def update_camera(renderer, cameraData):
+ camera = renderer.GetActiveCamera()
+ camera.SetPosition(cameraData["position"])
+ camera.SetFocalPoint(cameraData["focalPoint"])
+ camera.SetViewUp(cameraData["viewUp"])
+
+
+def create_spherical_camera(renderer, dataHandler, phiValues, thetaValues):
+ camera = renderer.GetActiveCamera()
+ return SphericalCamera(
+ dataHandler,
+ camera.GetFocalPoint(),
+ camera.GetPosition(),
+ camera.GetViewUp(),
+ phiValues,
+ thetaValues,
+ )
+
+
+def create_cylindrical_camera(renderer, dataHandler, phiValues, translationValues):
+ camera = renderer.GetActiveCamera()
+ return CylindricalCamera(
+ dataHandler,
+ camera.GetFocalPoint(),
+ camera.GetPosition(),
+ camera.GetViewUp(),
+ phiValues,
+ translationValues,
+ )
--- /dev/null
+import json, os, gzip, shutil
+
+from vtkmodules.vtkRenderingCore import vtkWindowToImageFilter
+from vtkmodules.vtkIOImage import vtkPNGReader, vtkPNGWriter, vtkJPEGWriter
+from vtkmodules.vtkCommonDataModel import vtkImageData
+from vtkmodules.vtkCommonCore import vtkUnsignedCharArray
+from vtkmodules.vtkFiltersParallel import vtkPResampleFilter
+
+from vtkmodules.web import iteritems, getJSArrayType
+from vtkmodules.web.camera import (
+ update_camera,
+ create_spherical_camera,
+ create_cylindrical_camera,
+)
+from vtkmodules.web.query_data_model import DataHandler
+
+# Global helper variables
+encode_codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+# -----------------------------------------------------------------------------
+# Capture image from render window
+# -----------------------------------------------------------------------------
+
+
+class CaptureRenderWindow(object):
+ def __init__(self, magnification=1):
+ self.windowToImage = vtkWindowToImageFilter()
+ self.windowToImage.SetScale(magnification)
+ self.windowToImage.SetInputBufferTypeToRGB()
+ self.windowToImage.ReadFrontBufferOn()
+ self.writer = None
+
+ def SetRenderWindow(self, renderWindow):
+ self.windowToImage.SetInput(renderWindow)
+
+ def SetFormat(self, mimeType):
+ if mimeType == "image/png":
+ self.writer = vtkPNGWriter()
+ self.writer.SetInputConnection(self.windowToImage.GetOutputPort())
+ elif mimeType == "image/jpg":
+ self.writer = vtkJPEGWriter()
+ self.writer.SetInputConnection(self.windowToImage.GetOutputPort())
+
+ def writeImage(self, path):
+ if self.writer:
+ self.windowToImage.Modified()
+ self.windowToImage.Update()
+ self.writer.SetFileName(path)
+ self.writer.Write()
+
+
+# -----------------------------------------------------------------------------
+# Basic Dataset Builder
+# -----------------------------------------------------------------------------
+
+
+class DataSetBuilder(object):
+ def __init__(self, location, camera_data, metadata={}, sections={}):
+ self.dataHandler = DataHandler(location)
+ self.cameraDescription = camera_data
+ self.camera = None
+ self.imageCapture = CaptureRenderWindow()
+
+ for key, value in iteritems(metadata):
+ self.dataHandler.addMetaData(key, value)
+
+ for key, value in iteritems(sections):
+ self.dataHandler.addSection(key, value)
+
+ def getDataHandler(self):
+ return self.dataHandler
+
+ def getCamera(self):
+ return self.camera
+
+ def updateCamera(self, camera):
+ update_camera(self.renderer, camera)
+ self.renderWindow.Render()
+
+ def start(self, renderWindow=None, renderer=None):
+ if renderWindow:
+ # Keep track of renderWindow and renderer
+ self.renderWindow = renderWindow
+ self.renderer = renderer
+
+ # Initialize image capture
+ self.imageCapture.SetRenderWindow(renderWindow)
+
+ # Handle camera if any
+ if self.cameraDescription:
+ if self.cameraDescription["type"] == "spherical":
+ self.camera = create_spherical_camera(
+ renderer,
+ self.dataHandler,
+ self.cameraDescription["phi"],
+ self.cameraDescription["theta"],
+ )
+ elif self.cameraDescription["type"] == "cylindrical":
+ self.camera = create_cylindrical_camera(
+ renderer,
+ self.dataHandler,
+ self.cameraDescription["phi"],
+ self.cameraDescription["translation"],
+ )
+
+ # Update background color
+ bgColor = renderer.GetBackground()
+ bgColorString = "rgb(%d, %d, %d)" % tuple(
+ int(bgColor[i] * 255) for i in range(3)
+ )
+ self.dataHandler.addMetaData("backgroundColor", bgColorString)
+
+ # Update file patterns
+ self.dataHandler.updateBasePattern()
+
+ def stop(self):
+ self.dataHandler.writeDataDescriptor()
+
+
+# -----------------------------------------------------------------------------
+# Image Dataset Builder
+# -----------------------------------------------------------------------------
+
+
+class ImageDataSetBuilder(DataSetBuilder):
+ def __init__(self, location, imageMimeType, cameraInfo, metadata={}, sections={}):
+ DataSetBuilder.__init__(self, location, cameraInfo, metadata, sections)
+ imageExtenstion = "." + imageMimeType.split("/")[1]
+ self.dataHandler.registerData(
+ name="image", type="blob", mimeType=imageMimeType, fileName=imageExtenstion
+ )
+ self.imageCapture.SetFormat(imageMimeType)
+
+ def writeImage(self):
+ self.imageCapture.writeImage(self.dataHandler.getDataAbsoluteFilePath("image"))
+
+ def writeImages(self):
+ for cam in self.camera:
+ update_camera(self.renderer, cam)
+ self.renderWindow.Render()
+ self.imageCapture.writeImage(
+ self.dataHandler.getDataAbsoluteFilePath("image")
+ )
+
+
+# -----------------------------------------------------------------------------
+# Volume Composite Dataset Builder
+# -----------------------------------------------------------------------------
+class VolumeCompositeDataSetBuilder(DataSetBuilder):
+ def __init__(self, location, imageMimeType, cameraInfo, metadata={}, sections={}):
+ DataSetBuilder.__init__(self, location, cameraInfo, metadata, sections)
+
+ self.dataHandler.addTypes("volume-composite", "rgba+depth")
+
+ self.imageMimeType = imageMimeType
+ self.imageExtenstion = "." + imageMimeType.split("/")[1]
+
+ if imageMimeType == "image/png":
+ self.imageWriter = vtkPNGWriter()
+ if imageMimeType == "image/jpg":
+ self.imageWriter = vtkJPEGWriter()
+
+ self.imageDataColor = vtkImageData()
+ self.imageWriter.SetInputData(self.imageDataColor)
+
+ self.imageDataDepth = vtkImageData()
+ self.depthToWrite = None
+
+ self.layerInfo = {}
+ self.colorByMapping = {}
+ self.compositePipeline = {
+ "layers": [],
+ "dimensions": [],
+ "fields": {},
+ "layer_fields": {},
+ "pipeline": [],
+ }
+ self.activeDepthKey = ""
+ self.activeRGBKey = ""
+ self.nodeWithChildren = {}
+
+ def _getColorCode(self, colorBy):
+ if colorBy in self.colorByMapping:
+ # The color code exist
+ return self.colorByMapping[colorBy]
+ else:
+ # No color code assigned yet
+ colorCode = encode_codes[len(self.colorByMapping)]
+ # Assign color code
+ self.colorByMapping[colorBy] = colorCode
+ # Register color code with color by value
+ self.compositePipeline["fields"][colorCode] = colorBy
+ # Return the color code
+ return colorCode
+
+ def _getLayerCode(self, parent, layerName):
+ if layerName in self.layerInfo:
+ # Layer already exist
+ return (self.layerInfo[layerName]["code"], False)
+ else:
+ layerCode = encode_codes[len(self.layerInfo)]
+ self.layerInfo[layerName] = {
+ "code": layerCode,
+ "name": layerName,
+ "parent": parent,
+ }
+ self.compositePipeline["layers"].append(layerCode)
+ self.compositePipeline["layer_fields"][layerCode] = []
+
+ # Let's register it in the pipeline
+ if parent:
+ if parent not in self.nodeWithChildren:
+ # Need to create parent
+ rootNode = {"name": parent, "ids": [], "children": []}
+ self.nodeWithChildren[parent] = rootNode
+ self.compositePipeline["pipeline"].append(rootNode)
+
+ # Add node to its parent
+ self.nodeWithChildren[parent]["children"].append(
+ {"name": layerName, "ids": [layerCode]}
+ )
+ self.nodeWithChildren[parent]["ids"].append(layerCode)
+
+ else:
+ self.compositePipeline["pipeline"].append(
+ {"name": layerName, "ids": [layerCode]}
+ )
+
+ return (layerCode, True)
+
+ def _needToRegisterColor(self, layerCode, colorCode):
+ if colorCode in self.compositePipeline["layer_fields"][layerCode]:
+ return False
+ else:
+ self.compositePipeline["layer_fields"][layerCode].append(colorCode)
+ return True
+
+ def activateLayer(self, parent, name, colorBy):
+ layerCode, needToRegisterDepth = self._getLayerCode(parent, name)
+ colorCode = self._getColorCode(colorBy)
+ needToRegisterColor = self._needToRegisterColor(layerCode, colorCode)
+
+ # Update active keys
+ self.activeDepthKey = "%s_depth" % layerCode
+ self.activeRGBKey = "%s%s_rgb" % (layerCode, colorCode)
+
+ # Need to register data
+ if needToRegisterDepth:
+ self.dataHandler.registerData(
+ name=self.activeDepthKey,
+ type="array",
+ fileName="/%s_depth.uint8" % layerCode,
+ categories=[layerCode],
+ )
+
+ if needToRegisterColor:
+ self.dataHandler.registerData(
+ name=self.activeRGBKey,
+ type="blob",
+ fileName="/%s%s_rgb%s" % (layerCode, colorCode, self.imageExtenstion),
+ categories=["%s%s" % (layerCode, colorCode)],
+ mimeType=self.imageMimeType,
+ )
+
+ def writeData(self, mapper):
+ width = self.renderWindow.GetSize()[0]
+ height = self.renderWindow.GetSize()[1]
+
+ if not self.depthToWrite:
+ self.depthToWrite = bytearray(width * height)
+
+ for cam in self.camera:
+ self.updateCamera(cam)
+ imagePath = self.dataHandler.getDataAbsoluteFilePath(self.activeRGBKey)
+ depthPath = self.dataHandler.getDataAbsoluteFilePath(self.activeDepthKey)
+
+ # -----------------------------------------------------------------
+ # Write Image
+ # -----------------------------------------------------------------
+ mapper.GetColorImage(self.imageDataColor)
+ self.imageWriter.SetFileName(imagePath)
+ self.imageWriter.Write()
+
+ # -----------------------------------------------------------------
+ # Write Depth
+ # -----------------------------------------------------------------
+ mapper.GetDepthImage(self.imageDataDepth)
+ inputArray = self.imageDataDepth.GetPointData().GetArray(0)
+ size = inputArray.GetNumberOfTuples()
+ for idx in range(size):
+ self.depthToWrite[idx] = int(inputArray.GetValue(idx))
+
+ with open(depthPath, "wb") as f:
+ f.write(self.depthToWrite)
+
+ def start(self, renderWindow, renderer):
+ DataSetBuilder.start(self, renderWindow, renderer)
+ self.camera.updatePriority([2, 1])
+
+ def stop(self, compress=True):
+ # Push metadata
+ self.compositePipeline["dimensions"] = self.renderWindow.GetSize()
+ self.compositePipeline["default_pipeline"] = (
+ "A".join(self.compositePipeline["layers"]) + "A"
+ )
+ self.dataHandler.addSection("CompositePipeline", self.compositePipeline)
+
+ # Write metadata
+ DataSetBuilder.stop(self)
+
+ if compress:
+ for root, dirs, files in os.walk(self.dataHandler.getBasePath()):
+ print("Compress", root)
+ for name in files:
+ if ".uint8" in name and ".gz" not in name:
+ with open(os.path.join(root, name), "rb") as f_in:
+ with gzip.open(
+ os.path.join(root, name + ".gz"), "wb"
+ ) as f_out:
+ shutil.copyfileobj(f_in, f_out)
+ os.remove(os.path.join(root, name))
+
+
+# -----------------------------------------------------------------------------
+# Data Prober Dataset Builder
+# -----------------------------------------------------------------------------
+class DataProberDataSetBuilder(DataSetBuilder):
+ def __init__(
+ self,
+ location,
+ sampling_dimesions,
+ fields_to_keep,
+ custom_probing_bounds=None,
+ metadata={},
+ ):
+ DataSetBuilder.__init__(self, location, None, metadata)
+ self.fieldsToWrite = fields_to_keep
+ self.resamplerFilter = vtkPResampleFilter()
+ self.resamplerFilter.SetSamplingDimension(sampling_dimesions)
+ if custom_probing_bounds:
+ self.resamplerFilter.SetUseInputBounds(0)
+ self.resamplerFilter.SetCustomSamplingBounds(custom_probing_bounds)
+ else:
+ self.resamplerFilter.SetUseInputBounds(1)
+
+ # Register all fields
+ self.dataHandler.addTypes("data-prober", "binary")
+ self.DataProber = {
+ "types": {},
+ "dimensions": sampling_dimesions,
+ "ranges": {},
+ "spacing": [1, 1, 1],
+ }
+ for field in self.fieldsToWrite:
+ self.dataHandler.registerData(
+ name=field, type="array", fileName="/%s.array" % field
+ )
+
+ def setDataToProbe(self, dataset):
+ self.resamplerFilter.SetInputData(dataset)
+
+ def setSourceToProbe(self, source):
+ self.resamplerFilter.SetInputConnection(source.GetOutputPort())
+
+ def writeData(self):
+ self.resamplerFilter.Update()
+ arrays = self.resamplerFilter.GetOutput().GetPointData()
+ for field in self.fieldsToWrite:
+ array = arrays.GetArray(field)
+ if array:
+ b = memoryview(array)
+ with open(self.dataHandler.getDataAbsoluteFilePath(field), "wb") as f:
+ f.write(b)
+
+ self.DataProber["types"][field] = getJSArrayType(array)
+ if field in self.DataProber["ranges"]:
+ dataRange = array.GetRange()
+ if dataRange[0] < self.DataProber["ranges"][field][0]:
+ self.DataProber["ranges"][field][0] = dataRange[0]
+ if dataRange[1] > self.DataProber["ranges"][field][1]:
+ self.DataProber["ranges"][field][1] = dataRange[1]
+ else:
+ self.DataProber["ranges"][field] = [
+ array.GetRange()[0],
+ array.GetRange()[1],
+ ]
+ else:
+ print("No array for", field)
+ print(self.resamplerFilter.GetOutput())
+
+ def stop(self, compress=True):
+ # Push metadata
+ self.dataHandler.addSection("DataProber", self.DataProber)
+
+ # Write metadata
+ DataSetBuilder.stop(self)
+
+ if compress:
+ for root, dirs, files in os.walk(self.dataHandler.getBasePath()):
+ print("Compress", root)
+ for name in files:
+ if ".array" in name and ".gz" not in name:
+ with open(os.path.join(root, name), "rb") as f_in:
+ with gzip.open(
+ os.path.join(root, name + ".gz"), "wb"
+ ) as f_out:
+ shutil.copyfileobj(f_in, f_out)
+ os.remove(os.path.join(root, name))
+
+
+# -----------------------------------------------------------------------------
+# Sorted Composite Dataset Builder
+# -----------------------------------------------------------------------------
+class ConvertVolumeStackToSortedStack(object):
+ def __init__(self, width, height):
+ self.width = width
+ self.height = height
+ self.layers = 0
+
+ def convert(self, directory):
+ imagePaths = {}
+ depthPaths = {}
+ layerNames = []
+ for fileName in os.listdir(directory):
+ if "_rgb" in fileName or "_depth" in fileName:
+ fileId = fileName.split("_")[0][0]
+ if "_rgb" in fileName:
+ imagePaths[fileId] = os.path.join(directory, fileName)
+ else:
+ layerNames.append(fileId)
+ depthPaths[fileId] = os.path.join(directory, fileName)
+
+ layerNames.sort()
+
+ if len(layerNames) == 0:
+ return
+
+ # Load data in Memory
+ depthArrays = []
+ imageReader = vtkPNGReader()
+ numberOfValues = self.width * self.height * len(layerNames)
+ imageSize = self.width * self.height
+ self.layers = len(layerNames)
+
+ # Write all images as single memoryview
+ opacity = vtkUnsignedCharArray()
+ opacity.SetNumberOfComponents(1)
+ opacity.SetNumberOfTuples(numberOfValues)
+
+ intensity = vtkUnsignedCharArray()
+ intensity.SetNumberOfComponents(1)
+ intensity.SetNumberOfTuples(numberOfValues)
+
+ for layer in range(self.layers):
+ imageReader.SetFileName(imagePaths[layerNames[layer]])
+ imageReader.Update()
+
+ rgbaArray = imageReader.GetOutput().GetPointData().GetArray(0)
+
+ for idx in range(imageSize):
+ intensity.SetValue(
+ (layer * imageSize) + idx, rgbaArray.GetValue(idx * 4)
+ )
+ opacity.SetValue(
+ (layer * imageSize) + idx, rgbaArray.GetValue(idx * 4 + 3)
+ )
+
+ with open(depthPaths[layerNames[layer]], "rb") as depthFile:
+ depthArrays.append(depthFile.read())
+
+ # Apply pixel sorting
+ destOrder = vtkUnsignedCharArray()
+ destOrder.SetNumberOfComponents(1)
+ destOrder.SetNumberOfTuples(numberOfValues)
+
+ opacityOrder = vtkUnsignedCharArray()
+ opacityOrder.SetNumberOfComponents(1)
+ opacityOrder.SetNumberOfTuples(numberOfValues)
+
+ intensityOrder = vtkUnsignedCharArray()
+ intensityOrder.SetNumberOfComponents(1)
+ intensityOrder.SetNumberOfTuples(numberOfValues)
+
+ for pixelIdx in range(imageSize):
+ depthStack = []
+ for depthArray in depthArrays:
+ depthStack.append((depthArray[pixelIdx], len(depthStack)))
+ depthStack.sort(key=lambda tup: tup[0])
+
+ for destLayerIdx in range(len(depthStack)):
+ sourceLayerIdx = depthStack[destLayerIdx][1]
+
+ # Copy Idx
+ destOrder.SetValue(
+ (imageSize * destLayerIdx) + pixelIdx, sourceLayerIdx
+ )
+ opacityOrder.SetValue(
+ (imageSize * destLayerIdx) + pixelIdx,
+ opacity.GetValue((imageSize * sourceLayerIdx) + pixelIdx),
+ )
+ intensityOrder.SetValue(
+ (imageSize * destLayerIdx) + pixelIdx,
+ intensity.GetValue((imageSize * sourceLayerIdx) + pixelIdx),
+ )
+
+ with open(os.path.join(directory, "alpha.uint8"), "wb") as f:
+ f.write(memoryview(opacityOrder))
+
+ with open(os.path.join(directory, "intensity.uint8"), "wb") as f:
+ f.write(memoryview(intensityOrder))
+
+ with open(os.path.join(directory, "order.uint8"), "wb") as f:
+ f.write(memoryview(destOrder))
+
+
+class SortedCompositeDataSetBuilder(VolumeCompositeDataSetBuilder):
+ def __init__(self, location, cameraInfo, metadata={}, sections={}):
+ VolumeCompositeDataSetBuilder.__init__(
+ self, location, "image/png", cameraInfo, metadata, sections
+ )
+ self.dataHandler.addTypes("sorted-composite", "rgba")
+
+ # Register order and color textures
+ self.layerScalars = []
+ self.dataHandler.registerData(
+ name="order", type="array", fileName="/order.uint8"
+ )
+ self.dataHandler.registerData(
+ name="alpha", type="array", fileName="/alpha.uint8"
+ )
+ self.dataHandler.registerData(
+ name="intensity",
+ type="array",
+ fileName="/intensity.uint8",
+ categories=["intensity"],
+ )
+
+ def start(self, renderWindow, renderer):
+ VolumeCompositeDataSetBuilder.start(self, renderWindow, renderer)
+ imageSize = self.renderWindow.GetSize()
+ self.dataConverter = ConvertVolumeStackToSortedStack(imageSize[0], imageSize[1])
+
+ def activateLayer(self, colorBy, scalar):
+ VolumeCompositeDataSetBuilder.activateLayer(
+ self, "root", "%s" % scalar, colorBy
+ )
+ self.layerScalars.append(scalar)
+
+ def writeData(self, mapper):
+ VolumeCompositeDataSetBuilder.writeData(self, mapper)
+
+ # Fill data pattern
+ self.dataHandler.getDataAbsoluteFilePath("order")
+ self.dataHandler.getDataAbsoluteFilePath("alpha")
+ self.dataHandler.getDataAbsoluteFilePath("intensity")
+
+ def stop(self, clean=True, compress=True):
+ VolumeCompositeDataSetBuilder.stop(self, compress=False)
+
+ # Go through all directories and convert them
+ for root, dirs, files in os.walk(self.dataHandler.getBasePath()):
+ for name in dirs:
+ print("Process", os.path.join(root, name))
+ self.dataConverter.convert(os.path.join(root, name))
+
+ # Rename index.json to info_origin.json
+ os.rename(
+ os.path.join(self.dataHandler.getBasePath(), "index.json"),
+ os.path.join(self.dataHandler.getBasePath(), "index_origin.json"),
+ )
+
+ # Update index.json
+ with open(
+ os.path.join(self.dataHandler.getBasePath(), "index_origin.json"), "r"
+ ) as infoFile:
+ metadata = json.load(infoFile)
+ metadata["SortedComposite"] = {
+ "dimensions": metadata["CompositePipeline"]["dimensions"],
+ "layers": self.dataConverter.layers,
+ "scalars": self.layerScalars[0 : self.dataConverter.layers],
+ }
+
+ # Clean metadata
+ dataToKeep = []
+ del metadata["CompositePipeline"]
+ for item in metadata["data"]:
+ if item["name"] in ["order", "alpha", "intensity"]:
+ dataToKeep.append(item)
+ metadata["data"] = dataToKeep
+ metadata["type"] = ["tonic-query-data-model", "sorted-composite", "alpha"]
+
+ # Override index.json
+ with open(
+ os.path.join(self.dataHandler.getBasePath(), "index.json"), "w"
+ ) as newMetaFile:
+ newMetaFile.write(json.dumps(metadata))
+
+ # Clean temporary data
+ if clean:
+ for root, dirs, files in os.walk(self.dataHandler.getBasePath()):
+ print("Clean", root)
+ for name in files:
+ if (
+ "_rgb.png" in name
+ or "_depth.uint8" in name
+ or name == "index_origin.json"
+ ):
+ os.remove(os.path.join(root, name))
+
+ if compress:
+ for root, dirs, files in os.walk(self.dataHandler.getBasePath()):
+ print("Compress", root)
+ for name in files:
+ if ".uint8" in name and ".gz" not in name:
+ with open(os.path.join(root, name), "rb") as f_in:
+ with gzip.open(
+ os.path.join(root, name + ".gz"), "wb"
+ ) as f_out:
+ shutil.copyfileobj(f_in, f_out)
+ os.remove(os.path.join(root, name))
--- /dev/null
+WEB_DEPENDENCY_MISSING_MESSAGE = """Please install VTK's Web module dependencies.
+
+These include `wslink` and can be easily installed with vtk by using the
+`web` extra requirements option. For example:
+
+ pip install vtk[web]
+
+"""
+
+class WebDependencyMissingError(ImportError):
+ def __init__(self, message=WEB_DEPENDENCY_MISSING_MESSAGE):
+ super().__init__(message)
--- /dev/null
+r"""protocols is a module that contains a set of VTK Web related
+protocols that can be combined together to provide a flexible way to define
+very specific web application.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import os, sys, logging, types, inspect, traceback, re, base64, time
+
+from vtkmodules.vtkWebCore import vtkWebInteractionEvent
+
+from vtkmodules.web.errors import WebDependencyMissingError
+from vtkmodules.web.render_window_serializer import (
+ serializeInstance,
+ SynchronizationContext,
+ getReferenceId,
+ initializeSerializers,
+)
+
+try:
+ from wslink import schedule_callback
+ from wslink import register as exportRpc
+ from wslink.websocket import LinkProtocol
+except ImportError:
+ raise WebDependencyMissingError()
+
+# =============================================================================
+#
+# Base class for any VTK Web based protocol
+#
+# =============================================================================
+
+
+class vtkWebProtocol(LinkProtocol):
+ def getApplication(self):
+ return self.getSharedObject("app")
+
+ # no need for a setApplication anymore, but keep for compatibility
+ def setApplication(self, app):
+ pass
+
+ def mapIdToObject(self, id):
+ """
+ Maps global-id for a vtkObject to the vtkObject instance. May return None if the
+ id is not valid.
+ """
+ id = int(id)
+ if id <= 0:
+ return None
+ return self.getApplication().GetObjectIdMap().GetVTKObject(id)
+
+ def getGlobalId(self, obj):
+ """
+ Return the id for a given vtkObject
+ """
+ return self.getApplication().GetObjectIdMap().GetGlobalId(obj)
+
+ def freeObject(self, obj):
+ """
+ Delete the given vtkObject from the objectIdMap. Returns true if delete succeeded.
+ """
+ return self.getApplication().GetObjectIdMap().FreeObject(obj)
+
+ def freeObjectById(self, id):
+ """
+ Delete the vtkObject corresponding to the given objectId from the objectIdMap.
+ Returns true if delete succeeded.
+ """
+ return self.getApplication().GetObjectIdMap().FreeObjectById(id)
+
+ def getView(self, vid):
+ """
+ Returns the view for a given view ID, if vid is None then return the
+ current active view.
+ :param vid: The view ID
+ :type vid: str
+ """
+ v = self.mapIdToObject(vid)
+
+ if not v:
+ # Use active view is none provided.
+ v = self.getApplication().GetObjectIdMap().GetActiveObject("VIEW")
+ if not v:
+ raise Exception("no view provided: %s" % vid)
+
+ return v
+
+ def setActiveView(self, view):
+ """
+ Set a vtkRenderWindow to be the active one
+ """
+ self.getApplication().GetObjectIdMap().SetActiveObject("VIEW", view)
+
+
+# =============================================================================
+#
+# Handle Mouse interaction on any type of view
+#
+# =============================================================================
+
+
+class vtkWebMouseHandler(vtkWebProtocol):
+ @exportRpc("viewport.mouse.interaction")
+ def mouseInteraction(self, event):
+ """
+ RPC Callback for mouse interactions.
+ """
+ view = self.getView(event["view"])
+
+ buttons = 0
+ if event["buttonLeft"]:
+ buttons |= vtkWebInteractionEvent.LEFT_BUTTON
+ if event["buttonMiddle"]:
+ buttons |= vtkWebInteractionEvent.MIDDLE_BUTTON
+ if event["buttonRight"]:
+ buttons |= vtkWebInteractionEvent.RIGHT_BUTTON
+
+ modifiers = 0
+ if event["shiftKey"]:
+ modifiers |= vtkWebInteractionEvent.SHIFT_KEY
+ if event["ctrlKey"]:
+ modifiers |= vtkWebInteractionEvent.CTRL_KEY
+ if event["altKey"]:
+ modifiers |= vtkWebInteractionEvent.ALT_KEY
+ if event["metaKey"]:
+ modifiers |= vtkWebInteractionEvent.META_KEY
+
+ pvevent = vtkWebInteractionEvent()
+ pvevent.SetButtons(buttons)
+ pvevent.SetModifiers(modifiers)
+ if "x" in event:
+ pvevent.SetX(event["x"])
+ if "y" in event:
+ pvevent.SetY(event["y"])
+ if "scroll" in event:
+ pvevent.SetScroll(event["scroll"])
+ if event["action"] == "dblclick":
+ pvevent.SetRepeatCount(2)
+ # pvevent.SetKeyCode(event["charCode"])
+ retVal = self.getApplication().HandleInteractionEvent(view, pvevent)
+ del pvevent
+
+ if event["action"] == "down":
+ self.getApplication().InvokeEvent("StartInteractionEvent")
+
+ if event["action"] == "up":
+ self.getApplication().InvokeEvent("EndInteractionEvent")
+
+ if retVal:
+ self.getApplication().InvokeEvent("UpdateEvent")
+
+ return retVal
+
+ @exportRpc("viewport.mouse.zoom.wheel")
+ def updateZoomFromWheel(self, event):
+ if "Start" in event["type"]:
+ self.getApplication().InvokeEvent("StartInteractionEvent")
+
+ renderWindow = self.getView(event["view"])
+ if renderWindow and "spinY" in event:
+ zoomFactor = 1.0 - event["spinY"] / 10.0
+
+ camera = renderWindow.GetRenderers().GetFirstRenderer().GetActiveCamera()
+ fp = camera.GetFocalPoint()
+ pos = camera.GetPosition()
+ delta = [fp[i] - pos[i] for i in range(3)]
+ camera.Zoom(zoomFactor)
+
+ pos2 = camera.GetPosition()
+ camera.SetFocalPoint([pos2[i] + delta[i] for i in range(3)])
+ renderWindow.Modified()
+
+ if "End" in event["type"]:
+ self.getApplication().InvokeEvent("EndInteractionEvent")
+
+
+# =============================================================================
+#
+# Basic 3D Viewport API (Camera + Orientation + CenterOfRotation
+#
+# =============================================================================
+
+
+class vtkWebViewPort(vtkWebProtocol):
+ @exportRpc("viewport.camera.reset")
+ def resetCamera(self, viewId):
+ """
+ RPC callback to reset camera.
+ """
+ view = self.getView(viewId)
+ renderer = view.GetRenderers().GetFirstRenderer()
+ renderer.ResetCamera()
+
+ self.getApplication().InvalidateCache(view)
+ self.getApplication().InvokeEvent("UpdateEvent")
+
+ return str(self.getGlobalId(view))
+
+ @exportRpc("viewport.axes.orientation.visibility.update")
+ def updateOrientationAxesVisibility(self, viewId, showAxis):
+ """
+ RPC callback to show/hide OrientationAxis.
+ """
+ view = self.getView(viewId)
+ # FIXME seb: view.OrientationAxesVisibility = (showAxis if 1 else 0);
+
+ self.getApplication().InvalidateCache(view)
+ self.getApplication().InvokeEvent("UpdateEvent")
+
+ return str(self.getGlobalId(view))
+
+ @exportRpc("viewport.axes.center.visibility.update")
+ def updateCenterAxesVisibility(self, viewId, showAxis):
+ """
+ RPC callback to show/hide CenterAxesVisibility.
+ """
+ view = self.getView(viewId)
+ # FIXME seb: view.CenterAxesVisibility = (showAxis if 1 else 0);
+
+ self.getApplication().InvalidateCache(view)
+ self.getApplication().InvokeEvent("UpdateEvent")
+
+ return str(self.getGlobalId(view))
+
+ @exportRpc("viewport.camera.update")
+ def updateCamera(self, view_id, focal_point, view_up, position, forceUpdate=True):
+ view = self.getView(view_id)
+
+ camera = view.GetRenderers().GetFirstRenderer().GetActiveCamera()
+ camera.SetFocalPoint(focal_point)
+ camera.SetViewUp(view_up)
+ camera.SetPosition(position)
+
+ if forceUpdate:
+ self.getApplication().InvalidateCache(view)
+ self.getApplication().InvokeEvent("UpdateEvent")
+
+
+# =============================================================================
+#
+# Provide Image delivery mechanism (deprecated - will be removed in VTK 10+)
+#
+# =============================================================================
+
+
+class vtkWebViewPortImageDelivery(vtkWebProtocol):
+ @exportRpc("viewport.image.render")
+ def stillRender(self, options):
+ """
+ RPC Callback to render a view and obtain the rendered image.
+ """
+ beginTime = int(round(time.time() * 1000))
+ view = self.getView(options["view"])
+ size = [view.GetSize()[0], view.GetSize()[1]]
+ # use existing size, overridden only if options["size"] is set.
+ resize = size != options.get("size", size)
+ if resize:
+ size = options["size"]
+ if size[0] > 0 and size[1] > 0:
+ view.SetSize(size)
+ t = 0
+ if options and "mtime" in options:
+ t = options["mtime"]
+ quality = 100
+ if options and "quality" in options:
+ quality = options["quality"]
+ localTime = 0
+ if options and "localTime" in options:
+ localTime = options["localTime"]
+ reply = {}
+ app = self.getApplication()
+ if t == 0:
+ app.InvalidateCache(view)
+ reply["image"] = app.StillRenderToString(view, t, quality)
+ # Check that we are getting image size we have set. If not, wait until we
+ # do. The render call will set the actual window size.
+ tries = 10
+ while resize and list(view.GetSize()) != size and size != [0, 0] and tries > 0:
+ app.InvalidateCache(view)
+ reply["image"] = app.StillRenderToString(view, t, quality)
+ tries -= 1
+
+ reply["stale"] = app.GetHasImagesBeingProcessed(view)
+ reply["mtime"] = app.GetLastStillRenderToMTime()
+ reply["size"] = [view.GetSize()[0], view.GetSize()[1]]
+ reply["format"] = "jpeg;base64"
+ reply["global_id"] = str(self.getGlobalId(view))
+ reply["localTime"] = localTime
+
+ endTime = int(round(time.time() * 1000))
+ reply["workTime"] = endTime - beginTime
+
+ return reply
+
+
+# =============================================================================
+#
+# Provide publish-based Image delivery mechanism
+#
+# =============================================================================
+
+
+class vtkWebPublishImageDelivery(vtkWebProtocol):
+ def __init__(self, decode=True):
+ super(vtkWebPublishImageDelivery, self).__init__()
+ self.trackingViews = {}
+ self.lastStaleTime = 0
+ self.staleHandlerCount = 0
+ self.deltaStaleTimeBeforeRender = 0.5 # 0.5s
+ self.decode = decode
+ self.viewsInAnimations = []
+ self.targetFrameRate = 30.0
+ self.minFrameRate = 12.0
+ self.maxFrameRate = 30.0
+
+ def pushRender(self, vId, ignoreAnimation=False):
+ if vId not in self.trackingViews:
+ return
+
+ if not self.trackingViews[vId]["enabled"]:
+ return
+
+ if not ignoreAnimation and len(self.viewsInAnimations) > 0:
+ return
+
+ if "originalSize" not in self.trackingViews[vId]:
+ view = self.getView(vId)
+ self.trackingViews[vId]["originalSize"] = list(view.GetSize())
+
+ if "ratio" not in self.trackingViews[vId]:
+ self.trackingViews[vId]["ratio"] = 1
+
+ ratio = self.trackingViews[vId]["ratio"]
+ mtime = self.trackingViews[vId]["mtime"]
+ quality = self.trackingViews[vId]["quality"]
+ size = [int(s * ratio) for s in self.trackingViews[vId]["originalSize"]]
+
+ reply = self.stillRender(
+ {"view": vId, "mtime": mtime, "quality": quality, "size": size}
+ )
+ stale = reply["stale"]
+ if reply["image"]:
+ # depending on whether the app has encoding enabled:
+ if self.decode:
+ reply["image"] = base64.standard_b64decode(reply["image"])
+
+ reply["image"] = self.addAttachment(reply["image"])
+ reply["format"] = "jpeg"
+ # save mtime for next call.
+ self.trackingViews[vId]["mtime"] = reply["mtime"]
+ # echo back real ID, instead of -1 for 'active'
+ reply["id"] = vId
+ self.publish("viewport.image.push.subscription", reply)
+ if stale:
+ self.lastStaleTime = time.time()
+ if self.staleHandlerCount == 0:
+ self.staleHandlerCount += 1
+ schedule_callback(
+ self.deltaStaleTimeBeforeRender, lambda: self.renderStaleImage(vId)
+ )
+ else:
+ self.lastStaleTime = 0
+
+ def renderStaleImage(self, vId):
+ self.staleHandlerCount -= 1
+
+ if self.lastStaleTime != 0:
+ delta = time.time() - self.lastStaleTime
+ if delta >= self.deltaStaleTimeBeforeRender:
+ self.pushRender(vId)
+ else:
+ self.staleHandlerCount += 1
+ schedule_callback(
+ self.deltaStaleTimeBeforeRender - delta + 0.001,
+ lambda: self.renderStaleImage(vId),
+ )
+
+ def animate(self):
+ if len(self.viewsInAnimations) == 0:
+ return
+
+ nextAnimateTime = time.time() + 1.0 / self.targetFrameRate
+ for vId in self.viewsInAnimations:
+ self.pushRender(vId, True)
+
+ nextAnimateTime -= time.time()
+
+ if self.targetFrameRate > self.maxFrameRate:
+ self.targetFrameRate = self.maxFrameRate
+
+ if nextAnimateTime < 0:
+ if nextAnimateTime < -1.0:
+ self.targetFrameRate = 1
+ if self.targetFrameRate > self.minFrameRate:
+ self.targetFrameRate -= 1.0
+ schedule_callback(0.001, lambda: self.animate())
+ else:
+ if self.targetFrameRate < self.maxFrameRate and nextAnimateTime > 0.005:
+ self.targetFrameRate += 1.0
+ schedule_callback(nextAnimateTime, lambda: self.animate())
+
+ @exportRpc("viewport.image.animation.fps.max")
+ def setMaxFrameRate(self, fps=30):
+ self.maxFrameRate = fps
+
+ @exportRpc("viewport.image.animation.fps.get")
+ def getCurrentFrameRate(self):
+ return self.targetFrameRate
+
+ @exportRpc("viewport.image.animation.start")
+ def startViewAnimation(self, viewId="-1"):
+ sView = self.getView(viewId)
+ realViewId = str(self.getGlobalId(sView))
+
+ self.viewsInAnimations.append(realViewId)
+ if len(self.viewsInAnimations) == 1:
+ self.animate()
+
+ @exportRpc("viewport.image.animation.stop")
+ def stopViewAnimation(self, viewId="-1"):
+ sView = self.getView(viewId)
+ realViewId = str(self.getGlobalId(sView))
+
+ if realViewId in self.viewsInAnimations:
+ self.viewsInAnimations.remove(realViewId)
+
+ @exportRpc("viewport.image.push")
+ def imagePush(self, options):
+ sView = self.getView(options["view"])
+ realViewId = str(self.getGlobalId(sView))
+ # Make sure an image is pushed
+ self.getApplication().InvalidateCache(sView)
+ self.pushRender(realViewId)
+
+ # Internal function since the reply[image] is not
+ # JSON(serializable) it can not be an RPC one
+ def stillRender(self, options):
+ """
+ RPC Callback to render a view and obtain the rendered image.
+ """
+ beginTime = int(round(time.time() * 1000))
+ view = self.getView(options["view"])
+ size = view.GetSize()[0:2]
+ resize = size != options.get("size", size)
+ if resize:
+ size = options["size"]
+ if size[0] > 10 and size[1] > 10:
+ view.SetSize(size)
+ t = 0
+ if options and "mtime" in options:
+ t = options["mtime"]
+ quality = 100
+ if options and "quality" in options:
+ quality = options["quality"]
+ localTime = 0
+ if options and "localTime" in options:
+ localTime = options["localTime"]
+ reply = {}
+ app = self.getApplication()
+ if t == 0:
+ app.InvalidateCache(view)
+ if self.decode:
+ stillRender = app.StillRenderToString
+ else:
+ stillRender = app.StillRenderToBuffer
+ reply_image = stillRender(view, t, quality)
+
+ # Check that we are getting image size we have set if not wait until we
+ # do. The render call will set the actual window size.
+ tries = 10
+ while resize and list(view.GetSize()) != size and size != [0, 0] and tries > 0:
+ app.InvalidateCache(view)
+ reply_image = stillRender(view, t, quality)
+ tries -= 1
+
+ if (
+ not resize
+ and options
+ and ("clearCache" in options)
+ and options["clearCache"]
+ ):
+ app.InvalidateCache(view)
+ reply_image = stillRender(view, t, quality)
+
+ reply["stale"] = app.GetHasImagesBeingProcessed(view)
+ reply["mtime"] = app.GetLastStillRenderToMTime()
+ reply["size"] = view.GetSize()[0:2]
+ reply["memsize"] = reply_image.GetDataSize() if reply_image else 0
+ reply["format"] = "jpeg;base64" if self.decode else "jpeg"
+ reply["global_id"] = str(self.getGlobalId(view))
+ reply["localTime"] = localTime
+ if self.decode:
+ reply["image"] = reply_image
+ else:
+ # Convert the vtkUnsignedCharArray into a bytes object, required by Autobahn websockets
+ reply["image"] = memoryview(reply_image).tobytes() if reply_image else None
+
+ endTime = int(round(time.time() * 1000))
+ reply["workTime"] = endTime - beginTime
+
+ return reply
+
+ @exportRpc("viewport.image.push.observer.add")
+ def addRenderObserver(self, viewId):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = str(self.getGlobalId(sView))
+
+ if not realViewId in self.trackingViews:
+ observerCallback = lambda *args, **kwargs: self.pushRender(realViewId)
+ startCallback = lambda *args, **kwargs: self.startViewAnimation(realViewId)
+ stopCallback = lambda *args, **kwargs: self.stopViewAnimation(realViewId)
+ tag = self.getApplication().AddObserver("UpdateEvent", observerCallback)
+ tagStart = self.getApplication().AddObserver(
+ "StartInteractionEvent", startCallback
+ )
+ tagStop = self.getApplication().AddObserver(
+ "EndInteractionEvent", stopCallback
+ )
+ # TODO do we need self.getApplication().AddObserver('ResetActiveView', resetActiveView())
+ self.trackingViews[realViewId] = {
+ "tags": [tag, tagStart, tagStop],
+ "observerCount": 1,
+ "mtime": 0,
+ "enabled": True,
+ "quality": 100,
+ }
+ else:
+ # There is an observer on this view already
+ self.trackingViews[realViewId]["observerCount"] += 1
+
+ self.pushRender(realViewId)
+ return {"success": True, "viewId": realViewId}
+
+ @exportRpc("viewport.image.push.observer.remove")
+ def removeRenderObserver(self, viewId):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = str(self.getGlobalId(sView))
+
+ observerInfo = None
+ if realViewId in self.trackingViews:
+ observerInfo = self.trackingViews[realViewId]
+
+ if not observerInfo:
+ return {"error": "Unable to find subscription for view %s" % realViewId}
+
+ observerInfo["observerCount"] -= 1
+
+ if observerInfo["observerCount"] <= 0:
+ for tag in observerInfo["tags"]:
+ self.getApplication().RemoveObserver(tag)
+ del self.trackingViews[realViewId]
+
+ return {"result": "success"}
+
+ @exportRpc("viewport.image.push.quality")
+ def setViewQuality(self, viewId, quality, ratio=1):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = str(self.getGlobalId(sView))
+ observerInfo = None
+ if realViewId in self.trackingViews:
+ observerInfo = self.trackingViews[realViewId]
+
+ if not observerInfo:
+ return {"error": "Unable to find subscription for view %s" % realViewId}
+
+ observerInfo["quality"] = quality
+ observerInfo["ratio"] = ratio
+
+ # Update image size right now!
+ if "originalSize" in self.trackingViews[realViewId]:
+ size = [
+ int(s * ratio) for s in self.trackingViews[realViewId]["originalSize"]
+ ]
+ if hasattr(sView, "SetSize"):
+ sView.SetSize(size)
+ else:
+ sView.ViewSize = size
+
+ return {"result": "success"}
+
+ @exportRpc("viewport.image.push.original.size")
+ def setViewSize(self, viewId, width, height):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = str(self.getGlobalId(sView))
+ observerInfo = None
+ if realViewId in self.trackingViews:
+ observerInfo = self.trackingViews[realViewId]
+
+ if not observerInfo:
+ return {"error": "Unable to find subscription for view %s" % realViewId}
+
+ observerInfo["originalSize"] = [width, height]
+
+ return {"result": "success"}
+
+ @exportRpc("viewport.image.push.enabled")
+ def enableView(self, viewId, enabled):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = str(self.getGlobalId(sView))
+ observerInfo = None
+ if realViewId in self.trackingViews:
+ observerInfo = self.trackingViews[realViewId]
+
+ if not observerInfo:
+ return {"error": "Unable to find subscription for view %s" % realViewId}
+
+ observerInfo["enabled"] = enabled
+
+ return {"result": "success"}
+
+ @exportRpc("viewport.image.push.invalidate.cache")
+ def invalidateCache(self, viewId):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ self.getApplication().InvalidateCache(sView)
+ self.getApplication().InvokeEvent("UpdateEvent")
+ return {"result": "success"}
+
+
+# =============================================================================
+#
+# Provide Geometry delivery mechanism (WebGL) (deprecated - will be removed in VTK 10+)
+#
+# =============================================================================
+
+
+class vtkWebViewPortGeometryDelivery(vtkWebProtocol):
+ @exportRpc("viewport.webgl.metadata")
+ def getSceneMetaData(self, view_id):
+ view = self.getView(view_id)
+ data = self.getApplication().GetWebGLSceneMetaData(view)
+ return data
+
+ @exportRpc("viewport.webgl.data")
+ def getWebGLData(self, view_id, object_id, part):
+ view = self.getView(view_id)
+ data = self.getApplication().GetWebGLBinaryData(view, str(object_id), part - 1)
+ return data
+
+
+# =============================================================================
+#
+# Provide File/Directory listing
+#
+# =============================================================================
+
+
+class vtkWebFileBrowser(vtkWebProtocol):
+ def __init__(
+ self, basePath, name, excludeRegex=r"^\.|~$|^\$", groupRegex=r"[0-9]+\."
+ ):
+ """
+ Configure the way the WebFile browser will expose the server content.
+ - basePath: specify the base directory that we should start with
+ - name: Name of that base directory that will show up on the web
+ - excludeRegex: Regular expression of what should be excluded from the list of files/directories
+ """
+ self.baseDirectory = basePath
+ self.rootName = name
+ self.pattern = re.compile(excludeRegex)
+ self.gPattern = re.compile(groupRegex)
+
+ @exportRpc("file.server.directory.list")
+ def listServerDirectory(self, relativeDir="."):
+ """
+ RPC Callback to list a server directory relative to the basePath
+ provided at start-up.
+ """
+ path = [self.rootName]
+ if len(relativeDir) > len(self.rootName):
+ relativeDir = relativeDir[len(self.rootName) + 1 :]
+ path += relativeDir.replace("\\", "/").split("/")
+
+ currentPath = os.path.join(self.baseDirectory, relativeDir)
+ result = {
+ "label": relativeDir,
+ "files": [],
+ "dirs": [],
+ "groups": [],
+ "path": path,
+ }
+ if relativeDir == ".":
+ result["label"] = self.rootName
+ for file in os.listdir(currentPath):
+ if os.path.isfile(os.path.join(currentPath, file)) and not re.search(
+ self.pattern, file
+ ):
+ result["files"].append({"label": file, "size": -1})
+ elif os.path.isdir(os.path.join(currentPath, file)) and not re.search(
+ self.pattern, file
+ ):
+ result["dirs"].append(file)
+
+ # Filter files to create groups
+ files = result["files"]
+ files.sort()
+ groups = result["groups"]
+ groupIdx = {}
+ filesToRemove = []
+ for file in files:
+ fileSplit = re.split(self.gPattern, file["label"])
+ if len(fileSplit) == 2:
+ filesToRemove.append(file)
+ gName = "*.".join(fileSplit)
+ if gName in groupIdx:
+ groupIdx[gName]["files"].append(file["label"])
+ else:
+ groupIdx[gName] = {"files": [file["label"]], "label": gName}
+ groups.append(groupIdx[gName])
+ for file in filesToRemove:
+ gName = "*.".join(re.split(self.gPattern, file["label"]))
+ if len(groupIdx[gName]["files"]) > 1:
+ files.remove(file)
+ else:
+ groups.remove(groupIdx[gName])
+
+ return result
+
+
+# =============================================================================
+#
+# Provide an updated geometry delivery mechanism which better matches the
+# client-side rendering capability we have in vtk.js
+#
+# =============================================================================
+
+
+class vtkWebLocalRendering(vtkWebProtocol):
+ def __init__(self, **kwargs):
+ super(vtkWebLocalRendering, self).__init__()
+ initializeSerializers()
+ self.context = SynchronizationContext()
+ self.trackingViews = {}
+ self.mtime = 0
+
+ # RpcName: getArray => viewport.geometry.array.get
+ @exportRpc("viewport.geometry.array.get")
+ def getArray(self, dataHash, binary=False):
+ if binary:
+ return self.addAttachment(self.context.getCachedDataArray(dataHash, binary))
+ return self.context.getCachedDataArray(dataHash, binary)
+
+ # RpcName: addViewObserver => viewport.geometry.view.observer.add
+ @exportRpc("viewport.geometry.view.observer.add")
+ def addViewObserver(self, viewId):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = self.getApplication().GetObjectIdMap().GetGlobalId(sView)
+
+ def pushGeometry(newSubscription=False):
+ stateToReturn = self.getViewState(realViewId, newSubscription)
+ stateToReturn["mtime"] = 0 if newSubscription else self.mtime
+ self.mtime += 1
+ return stateToReturn
+
+ if not realViewId in self.trackingViews:
+ observerCallback = lambda *args, **kwargs: self.publish(
+ "viewport.geometry.view.subscription", pushGeometry()
+ )
+ tag = self.getApplication().AddObserver("UpdateEvent", observerCallback)
+ self.trackingViews[realViewId] = {"tags": [tag], "observerCount": 1}
+ else:
+ # There is an observer on this view already
+ self.trackingViews[realViewId]["observerCount"] += 1
+
+ self.publish("viewport.geometry.view.subscription", pushGeometry(True))
+ return {"success": True, "viewId": realViewId}
+
+ # RpcName: removeViewObserver => viewport.geometry.view.observer.remove
+ @exportRpc("viewport.geometry.view.observer.remove")
+ def removeViewObserver(self, viewId):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ realViewId = self.getApplication().GetObjectIdMap().GetGlobalId(sView)
+
+ observerInfo = None
+ if realViewId in self.trackingViews:
+ observerInfo = self.trackingViews[realViewId]
+
+ if not observerInfo:
+ return {"error": "Unable to find subscription for view %s" % realViewId}
+
+ observerInfo["observerCount"] -= 1
+
+ if observerInfo["observerCount"] <= 0:
+ for tag in observerInfo["tags"]:
+ self.getApplication().RemoveObserver(tag)
+ del self.trackingViews[realViewId]
+
+ return {"result": "success"}
+
+ # RpcName: getViewState => viewport.geometry.view.get.state
+ @exportRpc("viewport.geometry.view.get.state")
+ def getViewState(self, viewId, newSubscription=False):
+ sView = self.getView(viewId)
+ if not sView:
+ return {"error": "Unable to get view with id %s" % viewId}
+
+ self.context.setIgnoreLastDependencies(newSubscription)
+
+ # Get the active view and render window, use it to iterate over renderers
+ renderWindow = sView
+ renderer = renderWindow.GetRenderers().GetFirstRenderer()
+ camera = renderer.GetActiveCamera()
+ renderWindowId = self.getApplication().GetObjectIdMap().GetGlobalId(sView)
+ viewInstance = serializeInstance(
+ None, renderWindow, renderWindowId, self.context, 1
+ )
+ viewInstance["extra"] = {
+ "vtkRefId": getReferenceId(renderWindow),
+ "centerOfRotation": camera.GetFocalPoint(),
+ "camera": getReferenceId(camera),
+ }
+
+ self.context.setIgnoreLastDependencies(False)
+ self.context.checkForArraysToRelease()
+
+ if viewInstance:
+ return viewInstance
+
+ return None
--- /dev/null
+"""
+Core Module for Web Base Data Generation
+"""
+
+import sys, os, json
+
+from vtkmodules.web import iteritems
+
+
+class DataHandler(object):
+ def __init__(self, basePath):
+ self.__root = basePath
+ self.types = ["tonic-query-data-model"]
+ self.metadata = {}
+ self.data = {}
+ self.arguments = {}
+ self.current = {}
+ self.sections = {}
+ self.basePattern = None
+ self.priority = []
+ self.argOrder = []
+ self.realValues = {}
+ self.can_write = True
+
+ def getBasePath(self):
+ return self.__root
+
+ def updateBasePattern(self):
+ self.priority.sort(key=lambda item: item[1])
+ self.basePattern = ""
+ patternSeparator = ""
+ currentPriority = -1
+
+ for item in self.priority:
+ if currentPriority != -1:
+ if currentPriority == item[1]:
+ patternSeparator = "_"
+ else:
+ patternSeparator = "/"
+ currentPriority = item[1]
+ self.basePattern = "{%s}%s%s" % (
+ item[0],
+ patternSeparator,
+ self.basePattern,
+ )
+
+ def registerArgument(self, **kwargs):
+ """
+ We expect the following set of arguments
+ - priority
+ - name
+ - label (optional)
+ - values
+ - uiType
+ - defaultIdx
+ """
+ newArgument = {}
+ argName = kwargs["name"]
+ self.argOrder.append(argName)
+ for key, value in iteritems(kwargs):
+ if key == "priority":
+ self.priority.append([argName, value])
+ elif key == "values":
+ self.realValues[argName] = value
+ newArgument[key] = ["{value}".format(value=x) for x in value]
+ else:
+ newArgument[key] = value
+
+ self.arguments[argName] = newArgument
+
+ def updatePriority(self, argumentName, newPriority):
+ for item in self.priority:
+ if item[0] == argumentName:
+ item[1] = newPriority
+
+ def setArguments(self, **kwargs):
+ """
+ Update the arguments index
+ """
+ for key, value in iteritems(kwargs):
+ self.current[key] = value
+
+ def removeData(self, name):
+ del self.data[name]
+
+ def registerData(self, **kwargs):
+ """
+ name, type, mimeType, fileName, dependencies
+ """
+ newData = {"metadata": {}}
+ argName = kwargs["name"]
+ for key, value in iteritems(kwargs):
+ if key == "fileName":
+ if "rootFile" in kwargs and kwargs["rootFile"]:
+ newData["pattern"] = "{pattern}/%s" % value
+ else:
+ newData["pattern"] = "{pattern}%s" % value
+ else:
+ newData[key] = value
+
+ self.data[argName] = newData
+
+ def addDataMetaData(self, name, key, value):
+ self.data[name]["metadata"][key] = value
+
+ def getDataAbsoluteFilePath(self, name, createDirectories=True):
+ dataPattern = self.data[name]["pattern"]
+ if "{pattern}" in dataPattern:
+ if len(self.basePattern) == 0:
+ dataPattern = dataPattern.replace(
+ "{pattern}/", self.basePattern
+ ).replace("{pattern}", self.basePattern)
+ self.data[name]["pattern"] = dataPattern
+ else:
+ dataPattern = dataPattern.replace("{pattern}", self.basePattern)
+ self.data[name]["pattern"] = dataPattern
+
+ keyValuePair = {}
+ for key, value in iteritems(self.current):
+ keyValuePair[key] = self.arguments[key]["values"][value]
+
+ fullpath = os.path.join(self.__root, dataPattern.format(**keyValuePair))
+
+ if createDirectories and self.can_write:
+ if not os.path.exists(os.path.dirname(fullpath)):
+ os.makedirs(os.path.dirname(fullpath))
+
+ return fullpath
+
+ def addTypes(self, *args):
+ for arg in args:
+ self.types.append(arg)
+
+ def addMetaData(self, key, value):
+ self.metadata[key] = value
+
+ def addSection(self, key, value):
+ self.sections[key] = value
+
+ def computeDataPatterns(self):
+ if self.basePattern == None:
+ self.updateBasePattern()
+
+ for name in self.data:
+ dataPattern = self.data[name]["pattern"]
+ if "{pattern}" in dataPattern:
+ dataPattern = dataPattern.replace("{pattern}", self.basePattern)
+ self.data[name]["pattern"] = dataPattern
+
+ def __getattr__(self, name):
+ if self.basePattern == None:
+ self.updateBasePattern()
+
+ for i in range(len(self.arguments[name]["values"])):
+ self.current[name] = i
+ yield self.realValues[name][i]
+
+ def writeDataDescriptor(self):
+ if not self.can_write:
+ return
+
+ self.computeDataPatterns()
+
+ jsonData = {
+ "arguments_order": self.argOrder,
+ "type": self.types,
+ "arguments": self.arguments,
+ "metadata": self.metadata,
+ "data": [],
+ }
+
+ # Add sections
+ for key, value in iteritems(self.sections):
+ jsonData[key] = value
+
+ # Add data
+ for key, value in iteritems(self.data):
+ jsonData["data"].append(value)
+
+ filePathToWrite = os.path.join(self.__root, "index.json")
+ with open(filePathToWrite, "w") as fileToWrite:
+ fileToWrite.write(json.dumps(jsonData))
--- /dev/null
+import io
+import logging
+import struct
+import time
+import zipfile
+
+from vtkmodules.web import (
+ base64Encode,
+ hashDataArray,
+ getJSArrayType,
+ arrayTypesMapping,
+ getReferenceId,
+)
+
+from vtkmodules.vtkCommonCore import vtkTypeUInt32Array
+from vtkmodules.vtkFiltersGeometry import vtkCompositeDataGeometryFilter
+from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
+from vtkmodules.vtkRenderingCore import vtkColorTransferFunction
+
+logger = logging.getLogger(__name__)
+# Always DEBUG level for this logger. Users can change this
+logger.setLevel(logging.DEBUG)
+
+# -----------------------------------------------------------------------------
+# Array helpers
+# -----------------------------------------------------------------------------
+
+def zipCompression(name, data):
+ with io.BytesIO() as in_memory:
+ with zipfile.ZipFile(in_memory, mode="w") as zf:
+ zf.writestr("data/%s" % name, data, zipfile.ZIP_DEFLATED)
+ in_memory.seek(0)
+ return in_memory.read()
+
+
+def dataTableToList(dataTable):
+ dataType = arrayTypesMapping[dataTable.GetDataType()]
+ elementSize = struct.calcsize(dataType)
+ nbValues = dataTable.GetNumberOfValues()
+ nbComponents = dataTable.GetNumberOfComponents()
+ nbytes = elementSize * nbValues
+ if dataType != " ":
+ with io.BytesIO(memoryview(dataTable)) as stream:
+ data = list(struct.unpack(dataType * nbValues, stream.read(nbytes)))
+ return [
+ data[idx * nbComponents : (idx + 1) * nbComponents]
+ for idx in range(nbValues // nbComponents)
+ ]
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def linspace(start, stop, num):
+ delta = (stop - start) / (num - 1)
+ return [start + i * delta for i in range(num)]
+
+
+# -----------------------------------------------------------------------------
+# Convenience class for caching data arrays, storing computed sha sums, keeping
+# track of valid actors, etc...
+# -----------------------------------------------------------------------------
+
+
+class SynchronizationContext:
+ def __init__(self):
+ self.dataArrayCache = {}
+ self.lastDependenciesMapping = {}
+ self.ingoreLastDependencies = False
+
+ def setIgnoreLastDependencies(self, force):
+ self.ingoreLastDependencies = force
+
+ def cacheDataArray(self, pMd5, data):
+ self.dataArrayCache[pMd5] = data
+
+ def getCachedDataArray(self, pMd5, binary=False, compression=False):
+ cacheObj = self.dataArrayCache[pMd5]
+ array = cacheObj["array"]
+ cacheTime = cacheObj["mTime"]
+
+ if cacheTime != array.GetMTime():
+ logger.debug(" ***** ERROR: you asked for an old cache key! ***** ")
+
+ if array.GetDataType() == 12:
+ # IdType need to be converted to Uint32
+ arraySize = array.GetNumberOfTuples() * array.GetNumberOfComponents()
+ newArray = vtkTypeUInt32Array()
+ newArray.SetNumberOfTuples(arraySize)
+ for i in range(arraySize):
+ newArray.SetValue(i, -1 if array.GetValue(i) < 0 else array.GetValue(i))
+ pBuffer = memoryview(newArray)
+ else:
+ pBuffer = memoryview(array)
+
+ if binary:
+ # Convert the vtkUnsignedCharArray into a bytes object, required by
+ # Autobahn websockets
+ return (
+ pBuffer.tobytes()
+ if not compression
+ else zipCompression(pMd5, pBuffer.tobytes())
+ )
+
+ return base64Encode(
+ pBuffer if not compression else zipCompression(pMd5, pBuffer.tobytes())
+ )
+
+ def checkForArraysToRelease(self, timeWindow=20):
+ cutOffTime = time.time() - timeWindow
+ shasToDelete = []
+ for sha in self.dataArrayCache:
+ record = self.dataArrayCache[sha]
+ array = record["array"]
+ count = array.GetReferenceCount()
+
+ if count == 1 and record["ts"] < cutOffTime:
+ shasToDelete.append(sha)
+
+ for sha in shasToDelete:
+ del self.dataArrayCache[sha]
+
+ def getLastDependencyList(self, idstr):
+ lastDeps = []
+ if idstr in self.lastDependenciesMapping and not self.ingoreLastDependencies:
+ lastDeps = self.lastDependenciesMapping[idstr]
+ return lastDeps
+
+ def setNewDependencyList(self, idstr, depList):
+ self.lastDependenciesMapping[idstr] = depList
+
+ def buildDependencyCallList(self, idstr, newList, addMethod, removeMethod):
+ oldList = self.getLastDependencyList(idstr)
+
+ calls = []
+ calls += [[addMethod, [wrapId(x)]] for x in newList if x not in oldList]
+ calls += [[removeMethod, [wrapId(x)]] for x in oldList if x not in newList]
+
+ self.setNewDependencyList(idstr, newList)
+ return calls
+
+
+# -----------------------------------------------------------------------------
+# Global variables
+# -----------------------------------------------------------------------------
+
+SERIALIZERS = {}
+JS_CLASS_MAPPING = {}
+context = None
+
+# -----------------------------------------------------------------------------
+# Global API
+# -----------------------------------------------------------------------------
+
+
+def registerInstanceSerializer(name, method):
+ global SERIALIZERS
+ SERIALIZERS[name] = method
+
+def registerJSClass(vtk_class, js_class):
+ global JS_CLASS_MAPPING
+ JS_CLASS_MAPPING[vtk_class] = js_class
+
+def class_name(vtk_obj):
+ vtk_class = vtk_obj.GetClassName()
+ if vtk_class in JS_CLASS_MAPPING:
+ return JS_CLASS_MAPPING[vtk_class]
+
+ return vtk_class
+
+
+# -----------------------------------------------------------------------------
+
+
+def serializeInstance(parent, instance, instanceId, context, depth):
+ instanceType = class_name(instance)
+ serializer = SERIALIZERS[instanceType] if instanceType in SERIALIZERS else None
+
+ if serializer:
+ return serializer(parent, instance, instanceId, context, depth)
+
+ logger.error(f"!!!No serializer for {instanceType} with id {instanceId}")
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def initializeSerializers():
+ # Actors/viewProps
+ registerInstanceSerializer("vtkActor", genericActorSerializer)
+ registerInstanceSerializer("vtkOpenGLActor", genericActorSerializer)
+ registerInstanceSerializer("vtkPVLODActor", genericActorSerializer)
+
+ # Volume/viewProps
+ registerInstanceSerializer("vtkVolume", genericVolumeSerializer)
+
+ # Mappers
+ registerInstanceSerializer("vtkMapper", genericMapperSerializer)
+ registerInstanceSerializer("vtkDataSetMapper", genericMapperSerializer)
+ registerInstanceSerializer("vtkPolyDataMapper", genericMapperSerializer)
+ registerInstanceSerializer("vtkImageDataMapper", genericMapperSerializer)
+ registerInstanceSerializer("vtkOpenGLPolyDataMapper", genericMapperSerializer)
+ registerInstanceSerializer("vtkCompositePolyDataMapper2", genericMapperSerializer)
+ registerJSClass("vtkPolyDataMapper", "vtkMapper")
+ registerJSClass("vtkDataSetMapper", "vtkMapper")
+ registerJSClass("vtkOpenGLPolyDataMapper", "vtkMapper")
+ registerJSClass("vtkCompositePolyDataMapper2", "vtkMapper")
+
+ registerInstanceSerializer("vtkVolumeMapper", genericVolumeMapperSerializer)
+ registerInstanceSerializer("vtkFixedPointVolumeRayCastMapper", genericVolumeMapperSerializer)
+ registerJSClass("vtkFixedPointVolumeRayCastMapper", "vtkVolumeMapper")
+
+ # LookupTables/TransferFunctions
+ registerInstanceSerializer("vtkLookupTable", lookupTableSerializer2)
+ registerInstanceSerializer(
+ "vtkPVDiscretizableColorTransferFunction", discretizableColorTransferFunctionSerializer
+ )
+ registerInstanceSerializer(
+ "vtkColorTransferFunction", colorTransferFunctionSerializer
+ )
+ registerInstanceSerializer("vtkPiecewiseFunction", pwfSerializer)
+
+ # Textures
+ registerInstanceSerializer("vtkTexture", textureSerializer)
+ registerInstanceSerializer("vtkOpenGLTexture", textureSerializer)
+
+ # Property
+ registerInstanceSerializer("vtkProperty", propertySerializer)
+ registerInstanceSerializer("vtkOpenGLProperty", propertySerializer)
+
+ # VolumeProperty
+ registerInstanceSerializer("vtkVolumeProperty", volumePropertySerializer)
+
+ # Datasets
+ registerInstanceSerializer("vtkPolyData", polydataSerializer)
+ registerInstanceSerializer("vtkImageData", imagedataSerializer)
+ registerInstanceSerializer("vtkUnstructuredGrid", mergeToPolydataSerializer)
+ registerInstanceSerializer("vtkMultiBlockDataSet", mergeToPolydataSerializer)
+ registerInstanceSerializer("vtkStructuredPoints", imagedataSerializer)
+ registerJSClass("vtkStructuredPoints", "vtkImageData")
+
+
+ # RenderWindows
+ registerInstanceSerializer("vtkRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkCocoaRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkXOpenGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkWin32OpenGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkEGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkOpenVRRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkOpenXRRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkGenericOpenGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkOSOpenGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkOpenGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkIOSRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkExternalOpenGLRenderWindow", renderWindowSerializer)
+ registerInstanceSerializer("vtkOffscreenOpenGLRenderWindow", renderWindowSerializer)
+
+ # Renderers
+ registerInstanceSerializer("vtkRenderer", rendererSerializer)
+ registerInstanceSerializer("vtkOpenGLRenderer", rendererSerializer)
+
+ # Cameras
+ registerInstanceSerializer("vtkCamera", cameraSerializer)
+ registerInstanceSerializer("vtkOpenGLCamera", cameraSerializer)
+
+ # Lights
+ registerInstanceSerializer("vtkLight", lightSerializer)
+ registerInstanceSerializer("vtkPVLight", lightSerializer)
+ registerInstanceSerializer("vtkOpenGLLight", lightSerializer)
+
+ # Annotations (ScalarBar/CubeAxes
+ registerInstanceSerializer("vtkCubeAxesActor", cubeAxesSerializer)
+ registerInstanceSerializer("vtkScalarBarActor", scalarBarActorSerializer)
+
+
+# -----------------------------------------------------------------------------
+# Helper functions
+# -----------------------------------------------------------------------------
+
+
+def pad(depth):
+ padding = ""
+ for _ in range(depth):
+ padding += " "
+ return padding
+
+
+# -----------------------------------------------------------------------------
+
+
+def wrapId(idStr):
+ return "instance:${%s}" % idStr
+
+
+# -----------------------------------------------------------------------------
+
+dataArrayShaMapping = {}
+
+
+def digest(array):
+ objId = getReferenceId(array)
+
+ record = None
+ if objId in dataArrayShaMapping:
+ record = dataArrayShaMapping[objId]
+
+ if record and record["mtime"] == array.GetMTime():
+ return record["sha"]
+
+ record = {"sha": hashDataArray(array), "mtime": array.GetMTime()}
+
+ dataArrayShaMapping[objId] = record
+ return record["sha"]
+
+
+# -----------------------------------------------------------------------------
+
+
+def getRangeInfo(array, component):
+ r = array.GetRange(component)
+ compRange = {}
+ compRange["min"] = r[0]
+ compRange["max"] = r[1]
+ compRange["component"] = array.GetComponentName(component)
+ return compRange
+
+
+# -----------------------------------------------------------------------------
+
+
+def getArrayDescription(array, context):
+ if not array:
+ return None
+
+ pMd5 = digest(array)
+ context.cacheDataArray(
+ pMd5, {"array": array, "mTime": array.GetMTime(), "ts": time.time()}
+ )
+
+ root = {}
+ root["hash"] = pMd5
+ root["vtkClass"] = "vtkDataArray"
+ root["name"] = array.GetName()
+ root["dataType"] = getJSArrayType(array)
+ root["numberOfComponents"] = array.GetNumberOfComponents()
+ root["size"] = array.GetNumberOfComponents() * array.GetNumberOfTuples()
+ root["ranges"] = []
+ if root["numberOfComponents"] > 1:
+ for i in range(root["numberOfComponents"]):
+ root["ranges"].append(getRangeInfo(array, i))
+ root["ranges"].append(getRangeInfo(array, -1))
+ else:
+ root["ranges"].append(getRangeInfo(array, 0))
+
+ return root
+
+
+# -----------------------------------------------------------------------------
+
+
+def extractRequiredFields(
+ extractedFields, parent, dataset, context, requestedFields=["Normals", "TCoords"]
+):
+ arrays_to_export = set()
+ export_all = "*" in requestedFields
+ # Identify arrays to export
+ if not export_all:
+ # FIXME should evolve and support funky mapper which leverage many arrays
+ if parent and parent.IsA("vtkMapper"):
+ mapper = parent
+ scalarVisibility = mapper.GetScalarVisibility()
+ arrayAccessMode = mapper.GetArrayAccessMode()
+ colorArrayName = (
+ mapper.GetArrayName() if arrayAccessMode == 1 else mapper.GetArrayId()
+ )
+ # colorMode = mapper.GetColorMode()
+ scalarMode = mapper.GetScalarMode()
+ if scalarVisibility and scalarMode in (1, 3):
+ array_to_export = dataset.GetPointData().GetArray(colorArrayName)
+ if array_to_export is None:
+ array_to_export = dataset.GetPointData().GetScalars()
+ arrays_to_export.add(array_to_export)
+ if scalarVisibility and scalarMode in (2, 4):
+ array_to_export = dataset.GetCellData().GetArray(colorArrayName)
+ if array_to_export is None:
+ array_to_export = dataset.GetCellData().GetScalars()
+ arrays_to_export.add(array_to_export)
+ if scalarVisibility and scalarMode == 0:
+ array_to_export = dataset.GetPointData().GetScalars()
+ if array_to_export is None:
+ array_to_export = dataset.GetCellData().GetScalars()
+ arrays_to_export.add(array_to_export)
+
+ if parent and parent.IsA("vtkTexture") and dataset.GetPointData().GetScalars():
+ arrays_to_export.add(dataset.GetPointData().GetScalars())
+
+ arrays_to_export.update(
+ [
+ getattr(dataset.GetPointData(), "Get" + requestedField, lambda: None)()
+ for requestedField in requestedFields
+ ]
+ )
+
+ # Browse all arrays
+ for location, field_data in [
+ ("pointData", dataset.GetPointData()),
+ ("cellData", dataset.GetCellData()),
+ ]:
+ for array_index in range(field_data.GetNumberOfArrays()):
+ array = field_data.GetArray(array_index)
+ if export_all or array in arrays_to_export:
+ arrayMeta = getArrayDescription(array, context)
+ if arrayMeta:
+ arrayMeta["location"] = location
+ attribute = field_data.IsArrayAnAttribute(array_index)
+ arrayMeta["registration"] = (
+ "set" + field_data.GetAttributeTypeAsString(attribute)
+ if attribute >= 0
+ else "addArray"
+ )
+ extractedFields.append(arrayMeta)
+
+# -----------------------------------------------------------------------------
+# Concrete instance serializers
+# -----------------------------------------------------------------------------
+
+
+def genericActorSerializer(parent, actor, actorId, context, depth):
+ # This kind of actor has two "children" of interest, a property and a
+ # mapper
+ actorVisibility = actor.GetVisibility()
+ mapperInstance = None
+ propertyInstance = None
+ calls = []
+ dependencies = []
+
+ if actorVisibility:
+ mapper = None
+ if not hasattr(actor, "GetMapper"):
+ logger.debug("This actor does not have a GetMapper method")
+ else:
+ mapper = actor.GetMapper()
+
+ if mapper:
+ mapperId = getReferenceId(mapper)
+ mapperInstance = serializeInstance(
+ actor, mapper, mapperId, context, depth + 1
+ )
+ if mapperInstance:
+ dependencies.append(mapperInstance)
+ calls.append(["setMapper", [wrapId(mapperId)]])
+
+ prop = None
+ if hasattr(actor, "GetProperty"):
+ prop = actor.GetProperty()
+ else:
+ logger.debug("This actor does not have a GetProperty method")
+
+ if prop:
+ propId = getReferenceId(prop)
+ propertyInstance = serializeInstance(
+ actor, prop, propId, context, depth + 1
+ )
+ if propertyInstance:
+ dependencies.append(propertyInstance)
+ calls.append(["setProperty", [wrapId(propId)]])
+
+ # Handle texture if any
+ texture = None
+ if hasattr(actor, "GetTexture"):
+ texture = actor.GetTexture()
+ else:
+ logger.debug("This actor does not have a GetTexture method")
+
+ if texture:
+ textureId = getReferenceId(texture)
+ textureInstance = serializeInstance(
+ actor, texture, textureId, context, depth + 1
+ )
+ if textureInstance:
+ dependencies.append(textureInstance)
+ calls.append(["addTexture", [wrapId(textureId)]])
+
+ if actorVisibility == 0 or (mapperInstance and propertyInstance):
+ return {
+ "parent": getReferenceId(parent),
+ "id": actorId,
+ "type": class_name(actor),
+ "properties": {
+ # vtkProp
+ "visibility": actorVisibility,
+ "pickable": actor.GetPickable(),
+ "dragable": actor.GetDragable(),
+ "useBounds": actor.GetUseBounds(),
+ # vtkProp3D
+ "origin": actor.GetOrigin(),
+ "position": actor.GetPosition(),
+ "scale": actor.GetScale(),
+ # vtkActor
+ "forceOpaque": actor.GetForceOpaque(),
+ "forceTranslucent": actor.GetForceTranslucent(),
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def genericVolumeSerializer(parent, actor, actorId, context, depth):
+ # This kind of actor has two "children" of interest, a property and a
+ # mapper
+ actorVisibility = actor.GetVisibility()
+ mapperInstance = None
+ propertyInstance = None
+ calls = []
+ dependencies = []
+
+ if actorVisibility:
+ mapper = None
+ if not hasattr(actor, "GetMapper"):
+ logger.debug("This actor does not have a GetMapper method")
+ else:
+ mapper = actor.GetMapper()
+
+ if mapper:
+ mapperId = getReferenceId(mapper)
+ mapperInstance = serializeInstance(
+ actor, mapper, mapperId, context, depth + 1
+ )
+ if mapperInstance:
+ dependencies.append(mapperInstance)
+ calls.append(["setMapper", [wrapId(mapperId)]])
+
+ prop = None
+ if hasattr(actor, "GetProperty"):
+ prop = actor.GetProperty()
+ else:
+ logger.debug("This actor does not have a GetProperty method")
+
+ if prop:
+ propId = getReferenceId(prop)
+ propertyInstance = serializeInstance(
+ actor, prop, propId, context, depth + 1
+ )
+ if propertyInstance:
+ dependencies.append(propertyInstance)
+ calls.append(["setProperty", [wrapId(propId)]])
+
+ if actorVisibility == 0 or (mapperInstance and propertyInstance):
+ return {
+ "parent": getReferenceId(parent),
+ "id": actorId,
+ "type": class_name(actor),
+ "properties": {
+ # vtkProp
+ "visibility": actorVisibility,
+ "pickable": actor.GetPickable(),
+ "dragable": actor.GetDragable(),
+ "useBounds": actor.GetUseBounds(),
+ # vtkProp3D
+ "origin": actor.GetOrigin(),
+ "position": actor.GetPosition(),
+ "scale": actor.GetScale(),
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+ return None
+
+# -----------------------------------------------------------------------------
+
+
+def textureSerializer(parent, texture, textureId, context, depth):
+ # This kind of mapper requires us to get 2 items: input data and lookup
+ # table
+ dataObject = None
+ dataObjectInstance = None
+ calls = []
+ dependencies = []
+
+ if hasattr(texture, "GetInput"):
+ dataObject = texture.GetInput()
+ else:
+ logger.debug("This texture does not have GetInput method")
+
+ if dataObject:
+ dataObjectId = "%s-texture" % textureId
+ dataObjectInstance = serializeInstance(
+ texture, dataObject, dataObjectId, context, depth + 1
+ )
+ if dataObjectInstance:
+ dependencies.append(dataObjectInstance)
+ calls.append(["setInputData", [wrapId(dataObjectId)]])
+
+ if dataObjectInstance:
+ return {
+ "parent": getReferenceId(parent),
+ "id": textureId,
+ "type": "vtkTexture",
+ "properties": {
+ "interpolate": texture.GetInterpolate(),
+ "repeat": texture.GetRepeat(),
+ "edgeClamp": texture.GetEdgeClamp(),
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def genericMapperSerializer(parent, mapper, mapperId, context, depth):
+ # This kind of mapper requires us to get 2 items: input data and lookup
+ # table
+ dataObject = None
+ dataObjectInstance = None
+ lookupTableInstance = None
+ calls = []
+ dependencies = []
+
+ if hasattr(mapper, "GetInputDataObject"):
+ mapper.GetInputAlgorithm().Update()
+ dataObject = mapper.GetInputDataObject(0, 0)
+ else:
+ logger.debug("This mapper does not have GetInputDataObject method")
+
+ if dataObject:
+ if dataObject.IsA("vtkDataSet"):
+ alg = vtkDataSetSurfaceFilter()
+ alg.SetInputData(dataObject)
+ alg.Update()
+ dataObject = alg.GetOutput()
+
+ dataObjectId = "%s-dataset" % mapperId
+ dataObjectInstance = serializeInstance(
+ mapper, dataObject, dataObjectId, context, depth + 1
+ )
+
+ if dataObjectInstance:
+ dependencies.append(dataObjectInstance)
+ calls.append(["setInputData", [wrapId(dataObjectId)]])
+
+ lookupTable = None
+
+ if hasattr(mapper, "GetLookupTable"):
+ lookupTable = mapper.GetLookupTable()
+ else:
+ logger.debug("This mapper does not have GetLookupTable method")
+
+ if lookupTable:
+ lookupTableId = getReferenceId(lookupTable)
+ lookupTableInstance = serializeInstance(
+ mapper, lookupTable, lookupTableId, context, depth + 1
+ )
+ if lookupTableInstance:
+ dependencies.append(lookupTableInstance)
+ calls.append(
+ ["setLookupTable", [wrapId(lookupTableId)]]
+ )
+
+ if dataObjectInstance:
+ colorArrayName = (
+ mapper.GetArrayName()
+ if mapper.GetArrayAccessMode() == 1
+ else mapper.GetArrayId()
+ )
+ return {
+ "parent": getReferenceId(parent),
+ "id": mapperId,
+ "type": class_name(mapper),
+ "properties": {
+ "resolveCoincidentTopology": mapper.GetResolveCoincidentTopology(),
+ "renderTime": mapper.GetRenderTime(),
+ "arrayAccessMode": mapper.GetArrayAccessMode(),
+ "scalarRange": mapper.GetScalarRange(),
+ "useLookupTableScalarRange": 1
+ if mapper.GetUseLookupTableScalarRange()
+ else 0,
+ "scalarVisibility": mapper.GetScalarVisibility(),
+ "colorByArrayName": colorArrayName,
+ "colorMode": mapper.GetColorMode(),
+ "scalarMode": mapper.GetScalarMode(),
+ "interpolateScalarsBeforeMapping": 1
+ if mapper.GetInterpolateScalarsBeforeMapping()
+ else 0,
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def genericVolumeMapperSerializer(parent, mapper, mapperId, context, depth):
+ # This kind of mapper requires us to get 2 items: input data and lookup
+ # table
+ dataObject = None
+ dataObjectInstance = None
+ lookupTableInstance = None
+ calls = []
+ dependencies = []
+
+ if hasattr(mapper, "GetInputDataObject"):
+ mapper.GetInputAlgorithm().Update()
+ dataObject = mapper.GetInputDataObject(0, 0)
+ else:
+ logger.debug("This mapper does not have GetInputDataObject method")
+
+ if dataObject:
+ dataObjectId = "%s-dataset" % mapperId
+ dataObjectInstance = serializeInstance(
+ mapper, dataObject, dataObjectId, context, depth + 1
+ )
+
+ if dataObjectInstance:
+ dependencies.append(dataObjectInstance)
+ calls.append(["setInputData", [wrapId(dataObjectId)]])
+
+ if dataObjectInstance:
+ return {
+ "parent": getReferenceId(parent),
+ "id": mapperId,
+ "type": class_name(mapper),
+ "properties": {
+ # VolumeMapper
+ "sampleDistance": mapper.GetSampleDistance(),
+ "imageSampleDistance": mapper.GetImageSampleDistance(),
+ # "maximumSamplesPerRay": mapper.GetMaximumSamplesPerRay(),
+ "autoAdjustSampleDistances": mapper.GetAutoAdjustSampleDistances(),
+ "blendMode": mapper.GetBlendMode(),
+ # "ipScalarRange": mapper.GetIpScalarRange(),
+ # "filterMode": mapper.GetFilterMode(),
+ # "preferSizeOverAccuracy": mapper.Get(),
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+ return None
+
+# -----------------------------------------------------------------------------
+
+
+def lookupTableSerializer(parent, lookupTable, lookupTableId, context, depth):
+ # No children in this case, so no additions to bindings and return empty list
+ # But we do need to add instance
+
+ lookupTableRange = lookupTable.GetRange()
+
+ lookupTableHueRange = [0.5, 0]
+ if hasattr(lookupTable, "GetHueRange"):
+ try:
+ lookupTable.GetHueRange(lookupTableHueRange)
+ except Exception as inst:
+ pass
+
+ lutSatRange = lookupTable.GetSaturationRange()
+ lutAlphaRange = lookupTable.GetAlphaRange()
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": lookupTableId,
+ "type": class_name(lookupTable),
+ "properties": {
+ "numberOfColors": lookupTable.GetNumberOfColors(),
+ "valueRange": lookupTableRange,
+ "hueRange": lookupTableHueRange,
+ # 'alphaRange': lutAlphaRange, # Causes weird rendering artifacts on client
+ "saturationRange": lutSatRange,
+ "nanColor": lookupTable.GetNanColor(),
+ "belowRangeColor": lookupTable.GetBelowRangeColor(),
+ "aboveRangeColor": lookupTable.GetAboveRangeColor(),
+ "useAboveRangeColor": True
+ if lookupTable.GetUseAboveRangeColor()
+ else False,
+ "useBelowRangeColor": True
+ if lookupTable.GetUseBelowRangeColor()
+ else False,
+ "alpha": lookupTable.GetAlpha(),
+ "vectorSize": lookupTable.GetVectorSize(),
+ "vectorComponent": lookupTable.GetVectorComponent(),
+ "vectorMode": lookupTable.GetVectorMode(),
+ "indexedLookup": lookupTable.GetIndexedLookup(),
+ },
+ }
+
+
+# -----------------------------------------------------------------------------
+
+
+def lookupTableToColorTransferFunction(lookupTable):
+ dataTable = lookupTable.GetTable()
+ table = dataTableToList(dataTable)
+ if table:
+ ctf = vtkColorTransferFunction()
+ tableRange = lookupTable.GetTableRange()
+ points = linspace(*tableRange, num=len(table))
+ for x, rgba in zip(points, table):
+ ctf.AddRGBPoint(x, *[x / 255 for x in rgba[:3]])
+
+ return ctf
+
+ return None
+
+
+def lookupTableSerializer2(parent, lookupTable, lookupTableId, context, depth):
+ ctf = lookupTableToColorTransferFunction(lookupTable)
+ if ctf:
+ return colorTransferFunctionSerializer(
+ parent, ctf, lookupTableId, context, depth
+ )
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def propertySerializer(parent, propObj, propObjId, context, depth):
+ representation = (
+ propObj.GetRepresentation() if hasattr(propObj, "GetRepresentation") else 2
+ )
+ colorToUse = (
+ propObj.GetDiffuseColor() if hasattr(propObj, "GetDiffuseColor") else [1, 1, 1]
+ )
+ if representation == 1 and hasattr(propObj, "GetColor"):
+ colorToUse = propObj.GetColor()
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": propObjId,
+ "type": class_name(propObj),
+ "properties": {
+ "representation": representation,
+ "diffuseColor": colorToUse,
+ "color": propObj.GetColor(),
+ "ambientColor": propObj.GetAmbientColor(),
+ "specularColor": propObj.GetSpecularColor(),
+ "edgeColor": propObj.GetEdgeColor(),
+ "ambient": propObj.GetAmbient(),
+ "diffuse": propObj.GetDiffuse(),
+ "specular": propObj.GetSpecular(),
+ "specularPower": propObj.GetSpecularPower(),
+ "opacity": propObj.GetOpacity(),
+ "interpolation": propObj.GetInterpolation(),
+ "edgeVisibility": 1 if propObj.GetEdgeVisibility() else 0,
+ "backfaceCulling": 1 if propObj.GetBackfaceCulling() else 0,
+ "frontfaceCulling": 1 if propObj.GetFrontfaceCulling() else 0,
+ "pointSize": propObj.GetPointSize(),
+ "lineWidth": propObj.GetLineWidth(),
+ "lighting": 1 if propObj.GetLighting() else 0,
+ },
+ }
+
+def volumePropertySerializer(parent, propObj, propObjId, context, depth):
+ calls = []
+ dependencies = []
+
+ # Color handling
+ lut = propObj.GetRGBTransferFunction()
+ if lut:
+ lookupTableId = getReferenceId(lut)
+ lookupTableInstance = serializeInstance(
+ propObj, lut, lookupTableId, context, depth + 1
+ )
+
+ if lookupTableInstance:
+ dependencies.append(lookupTableInstance)
+ calls.append(["setRGBTransferFunction", [0, wrapId(lookupTableId)]])
+
+ # Piecewise handling
+ pwf = propObj.GetScalarOpacity()
+ if pwf:
+ pwfId = getReferenceId(pwf)
+ pwfInstance = serializeInstance(
+ propObj, pwf, pwfId, context, depth + 1
+ )
+
+ if pwfInstance:
+ dependencies.append(pwfInstance)
+ calls.append(["setScalarOpacity", [0, wrapId(pwfId)]])
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": propObjId,
+ "type": class_name(propObj),
+ "properties": {
+ "independentComponents": propObj.GetIndependentComponents(),
+ "interpolationType": propObj.GetInterpolationType(),
+ "shade": propObj.GetShade(),
+ "ambient": propObj.GetAmbient(),
+ "diffuse": propObj.GetDiffuse(),
+ "specular": propObj.GetSpecular(),
+ "specularPower": propObj.GetSpecularPower(),
+ # "useLabelOutline": propObj.GetUseLabelOutline(),
+ # "labelOutlineThickness": propObj.GetLabelOutlineThickness(),
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+# -----------------------------------------------------------------------------
+
+
+def imagedataSerializer(parent, dataset, datasetId, context, depth, requested_fields = ["Normals", "TCoords"]):
+ if hasattr(dataset, "GetDirectionMatrix"):
+ direction = [dataset.GetDirectionMatrix().GetElement(0, i) for i in range(9)]
+ else:
+ direction = [1, 0, 0, 0, 1, 0, 0, 0, 1]
+
+ # Extract dataset fields
+ fields = []
+ extractRequiredFields(fields, parent, dataset, context, "*")
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": datasetId,
+ "type": class_name(dataset),
+ "properties": {
+ "spacing": dataset.GetSpacing(),
+ "origin": dataset.GetOrigin(),
+ "dimensions": dataset.GetDimensions(),
+ "direction": direction,
+ "fields": fields,
+ },
+ }
+
+
+# -----------------------------------------------------------------------------
+
+
+def polydataSerializer(parent, dataset, datasetId, context, depth, requested_fields = ["Normals", "TCoords"]):
+ if dataset and dataset.GetPoints():
+ properties = {}
+
+ # Points
+ points = getArrayDescription(dataset.GetPoints().GetData(), context)
+ points["vtkClass"] = "vtkPoints"
+ properties["points"] = points
+
+ # Verts
+ if dataset.GetVerts() and dataset.GetVerts().GetData().GetNumberOfTuples() > 0:
+ _verts = getArrayDescription(dataset.GetVerts().GetData(), context)
+ properties["verts"] = _verts
+ properties["verts"]["vtkClass"] = "vtkCellArray"
+
+ # Lines
+ if dataset.GetLines() and dataset.GetLines().GetData().GetNumberOfTuples() > 0:
+ _lines = getArrayDescription(dataset.GetLines().GetData(), context)
+ properties["lines"] = _lines
+ properties["lines"]["vtkClass"] = "vtkCellArray"
+
+ # Polys
+ if dataset.GetPolys() and dataset.GetPolys().GetData().GetNumberOfTuples() > 0:
+ _polys = getArrayDescription(dataset.GetPolys().GetData(), context)
+ properties["polys"] = _polys
+ properties["polys"]["vtkClass"] = "vtkCellArray"
+
+ # Strips
+ if (
+ dataset.GetStrips()
+ and dataset.GetStrips().GetData().GetNumberOfTuples() > 0
+ ):
+ _strips = getArrayDescription(dataset.GetStrips().GetData(), context)
+ properties["strips"] = _strips
+ properties["strips"]["vtkClass"] = "vtkCellArray"
+
+ # Fields
+ properties["fields"] = []
+ extractRequiredFields(properties["fields"], parent, dataset, context, requested_fields)
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": datasetId,
+ "type": class_name(dataset),
+ "properties": properties,
+ }
+
+ logger.debug("This dataset has no points!")
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def mergeToPolydataSerializer(parent, dataObject, dataObjectId, context, depth, requested_fields=["Normals", "TCoords"]):
+ dataset = None
+
+ if dataObject.IsA("vtkCompositeDataSet"):
+ gf = vtkCompositeDataGeometryFilter()
+ gf.SetInputData(dataObject)
+ gf.Update()
+ dataset = gf.GetOutput()
+ elif dataObject.IsA("vtkUnstructuredGrid"):
+ gf = vtkDataSetSurfaceFilter()
+ gf.SetInputData(dataObject)
+ gf.Update()
+ dataset = gf.GetOutput()
+ else:
+ dataset = mapper.GetInput()
+
+ return polydataSerializer(parent, dataset, dataObjectId, context, depth, requested_fields)
+
+
+# -----------------------------------------------------------------------------
+
+
+def colorTransferFunctionSerializer(parent, instance, objId, context, depth):
+ nodes = []
+
+ for i in range(instance.GetSize()):
+ # x, r, g, b, midpoint, sharpness
+ node = [0, 0, 0, 0, 0, 0]
+ instance.GetNodeValue(i, node)
+ nodes.append(node)
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": objId,
+ "type": class_name(instance),
+ "properties": {
+ "clamping": 1 if instance.GetClamping() else 0,
+ "colorSpace": instance.GetColorSpace(),
+ "hSVWrap": 1 if instance.GetHSVWrap() else 0,
+ # 'nanColor': instance.GetNanColor(), # Breaks client
+ # 'belowRangeColor': instance.GetBelowRangeColor(), # Breaks client
+ # 'aboveRangeColor': instance.GetAboveRangeColor(), # Breaks client
+ # 'useAboveRangeColor': 1 if instance.GetUseAboveRangeColor() else 0,
+ # 'useBelowRangeColor': 1 if instance.GetUseBelowRangeColor() else 0,
+ "allowDuplicateScalars": 1 if instance.GetAllowDuplicateScalars() else 0,
+ "alpha": instance.GetAlpha(),
+ "vectorComponent": instance.GetVectorComponent(),
+ "vectorSize": instance.GetVectorSize(),
+ "vectorMode": instance.GetVectorMode(),
+ "indexedLookup": instance.GetIndexedLookup(),
+ "nodes": nodes,
+ },
+ }
+
+def discretizableColorTransferFunctionSerializer(parent, instance, objId, context, depth):
+ ctf = colorTransferFunctionSerializer(parent, instance, objId, context, depth)
+ ctf["properties"]["discretize"] = instance.GetDiscretize()
+ ctf["properties"]["numberOfValues"] = instance.GetNumberOfValues()
+ return ctf
+
+# -----------------------------------------------------------------------------
+
+def pwfSerializer(parent, instance, objId, context, depth):
+ nodes = []
+
+ for i in range(instance.GetSize()):
+ # x, y, midpoint, sharpness
+ node = [0, 0, 0, 0]
+ instance.GetNodeValue(i, node)
+ nodes.append(node)
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": objId,
+ "type": class_name(instance),
+ "properties": {
+ "range": list(instance.GetRange()),
+ "clamping": instance.GetClamping(),
+ "allowDuplicateScalars": instance.GetAllowDuplicateScalars(),
+ "nodes": nodes,
+ },
+ }
+
+# -----------------------------------------------------------------------------
+
+def cubeAxesSerializer(parent, actor, actorId, context, depth):
+ """
+ Possible add-on properties for vtk.js:
+ gridLines: True,
+ axisLabels: None,
+ axisTitlePixelOffset: 35.0,
+ axisTextStyle: {
+ fontColor: 'white',
+ fontStyle: 'normal',
+ fontSize: 18,
+ fontFamily: 'serif',
+ },
+ tickLabelPixelOffset: 12.0,
+ tickTextStyle: {
+ fontColor: 'white',
+ fontStyle: 'normal',
+ fontSize: 14,
+ fontFamily: 'serif',
+ },
+ """
+ axisLabels = ["", "", ""]
+ if actor.GetXAxisLabelVisibility():
+ axisLabels[0] = actor.GetXTitle()
+ if actor.GetYAxisLabelVisibility():
+ axisLabels[1] = actor.GetYTitle()
+ if actor.GetZAxisLabelVisibility():
+ axisLabels[2] = actor.GetZTitle()
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": actorId,
+ "type": "vtkCubeAxesActor",
+ "properties": {
+ # vtkProp
+ "visibility": actor.GetVisibility(),
+ "pickable": actor.GetPickable(),
+ "dragable": actor.GetDragable(),
+ "useBounds": actor.GetUseBounds(),
+ # vtkProp3D
+ "origin": actor.GetOrigin(),
+ "position": actor.GetPosition(),
+ "scale": actor.GetScale(),
+ # vtkActor
+ "forceOpaque": actor.GetForceOpaque(),
+ "forceTranslucent": actor.GetForceTranslucent(),
+ # vtkCubeAxesActor
+ "dataBounds": actor.GetBounds(),
+ "faceVisibilityAngle": 8,
+ "gridLines": True,
+ "axisLabels": axisLabels,
+ "axisTitlePixelOffset": 35.0,
+ "axisTextStyle": {
+ "fontColor": "white",
+ "fontStyle": "normal",
+ "fontSize": 18,
+ "fontFamily": "serif",
+ },
+ "tickLabelPixelOffset": 12.0,
+ "tickTextStyle": {
+ "fontColor": "white",
+ "fontStyle": "normal",
+ "fontSize": 14,
+ "fontFamily": "serif",
+ },
+ },
+ "calls": [["setCamera", [wrapId(getReferenceId(actor.GetCamera()))]]],
+ "dependencies": [],
+ }
+
+# -----------------------------------------------------------------------------
+
+def scalarBarActorSerializer(parent, actor, actorId, context, depth):
+ dependencies = []
+ calls = []
+ lut = actor.GetLookupTable()
+ if not lut:
+ return None
+
+ lutId = getReferenceId(lut)
+ lutInstance = serializeInstance(actor, lut, lutId, context, depth + 1)
+ if not lutInstance:
+ return None
+
+ dependencies.append(lutInstance)
+ calls.append(["setScalarsToColors", [wrapId(lutId)]])
+
+ prop = None
+ if hasattr(actor, "GetProperty"):
+ prop = actor.GetProperty()
+ else:
+ logger.debug("This scalarBarActor does not have a GetProperty method")
+
+ if prop:
+ propId = getReferenceId(prop)
+ propertyInstance = serializeInstance(
+ actor, prop, propId, context, depth + 1
+ )
+ if propertyInstance:
+ dependencies.append(propertyInstance)
+ calls.append(["setProperty", [wrapId(propId)]])
+
+ axisLabel = actor.GetTitle()
+ width = actor.GetWidth()
+ height = actor.GetHeight()
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": actorId,
+ "type": "vtkScalarBarActor",
+ "properties": {
+ # vtkProp
+ "visibility": actor.GetVisibility(),
+ "pickable": actor.GetPickable(),
+ "dragable": actor.GetDragable(),
+ "useBounds": actor.GetUseBounds(),
+ # vtkActor2D
+ # "position": actor.GetPosition(),
+ # "position2": actor.GetPosition2(),
+ # "width": actor.GetWidth(),
+ # "height": actor.GetHeight(),
+ # vtkScalarBarActor
+ "automated": True,
+ "axisLabel": axisLabel,
+ # 'barPosition': [0, 0],
+ # 'barSize': [0, 0],
+ "boxPosition": [0.88, -0.92],
+ "boxSize": [width, height],
+ "axisTitlePixelOffset": 36.0,
+ "axisTextStyle": {
+ "fontColor": actor.GetTitleTextProperty().GetColor(),
+ "fontStyle": "normal",
+ "fontSize": 18,
+ "fontFamily": "serif",
+ },
+ "tickLabelPixelOffset": 14.0,
+ "tickTextStyle": {
+ "fontColor": actor.GetTitleTextProperty().GetColor(),
+ "fontStyle": "normal",
+ "fontSize": 14,
+ "fontFamily": "serif",
+ },
+ "drawNanAnnotation": actor.GetDrawNanAnnotation(),
+ "drawBelowRangeSwatch": actor.GetDrawBelowRangeSwatch(),
+ "drawAboveRangeSwatch": actor.GetDrawAboveRangeSwatch(),
+ },
+ "calls": calls,
+ "dependencies": dependencies,
+ }
+
+# -----------------------------------------------------------------------------
+
+
+def rendererSerializer(parent, instance, objId, context, depth):
+ dependencies = []
+ viewPropIds = []
+ lightsIds = []
+ calls = []
+
+ # Camera
+ camera = instance.GetActiveCamera()
+ cameraId = getReferenceId(camera)
+ cameraInstance = serializeInstance(instance, camera, cameraId, context, depth + 1)
+ if cameraInstance:
+ dependencies.append(cameraInstance)
+ calls.append(["setActiveCamera", [wrapId(cameraId)]])
+
+ # View prop as representation containers
+ viewPropCollection = instance.GetViewProps()
+ for rpIdx in range(viewPropCollection.GetNumberOfItems()):
+ viewProp = viewPropCollection.GetItemAsObject(rpIdx)
+ viewPropId = getReferenceId(viewProp)
+
+ viewPropInstance = serializeInstance(
+ instance, viewProp, viewPropId, context, depth + 1
+ )
+ if viewPropInstance:
+ dependencies.append(viewPropInstance)
+ viewPropIds.append(viewPropId)
+
+ calls += context.buildDependencyCallList(
+ "%s-props" % objId, viewPropIds, "addViewProp", "removeViewProp"
+ )
+
+ # Lights
+ lightCollection = instance.GetLights()
+ for lightIdx in range(lightCollection.GetNumberOfItems()):
+ light = lightCollection.GetItemAsObject(lightIdx)
+ lightId = getReferenceId(light)
+
+ lightInstance = serializeInstance(instance, light, lightId, context, depth + 1)
+ if lightInstance:
+ dependencies.append(lightInstance)
+ lightsIds.append(lightId)
+
+ calls += context.buildDependencyCallList(
+ "%s-lights" % objId, lightsIds, "addLight", "removeLight"
+ )
+
+ if len(dependencies) > 1:
+ return {
+ "parent": getReferenceId(parent),
+ "id": objId,
+ "type": class_name(instance),
+ "properties": {
+ "background": instance.GetBackground(),
+ "background2": instance.GetBackground2(),
+ "viewport": instance.GetViewport(),
+ # These commented properties do not yet have real setters in vtk.js
+ # 'gradientBackground': instance.GetGradientBackground(),
+ # 'aspect': instance.GetAspect(),
+ # 'pixelAspect': instance.GetPixelAspect(),
+ # 'ambient': instance.GetAmbient(),
+ "twoSidedLighting": instance.GetTwoSidedLighting(),
+ "lightFollowCamera": instance.GetLightFollowCamera(),
+ "layer": instance.GetLayer(),
+ "preserveColorBuffer": instance.GetPreserveColorBuffer(),
+ "preserveDepthBuffer": instance.GetPreserveDepthBuffer(),
+ "nearClippingPlaneTolerance": instance.GetNearClippingPlaneTolerance(),
+ "clippingRangeExpansion": instance.GetClippingRangeExpansion(),
+ "useShadows": instance.GetUseShadows(),
+ "useDepthPeeling": instance.GetUseDepthPeeling(),
+ "occlusionRatio": instance.GetOcclusionRatio(),
+ "maximumNumberOfPeels": instance.GetMaximumNumberOfPeels(),
+ "interactive": instance.GetInteractive(),
+ },
+ "dependencies": dependencies,
+ "calls": calls,
+ }
+
+ return None
+
+
+# -----------------------------------------------------------------------------
+
+
+def cameraSerializer(parent, instance, objId, context, depth):
+ return {
+ "parent": getReferenceId(parent),
+ "id": objId,
+ "type": class_name(instance),
+ "properties": {
+ "focalPoint": instance.GetFocalPoint(),
+ "position": instance.GetPosition(),
+ "viewUp": instance.GetViewUp(),
+ "clippingRange": instance.GetClippingRange(),
+ },
+ }
+
+
+# -----------------------------------------------------------------------------
+
+
+def lightTypeToString(value):
+ """
+ #define VTK_LIGHT_TYPE_HEADLIGHT 1
+ #define VTK_LIGHT_TYPE_CAMERA_LIGHT 2
+ #define VTK_LIGHT_TYPE_SCENE_LIGHT 3
+
+ 'HeadLight';
+ 'SceneLight';
+ 'CameraLight'
+ """
+ if value == 1:
+ return "HeadLight"
+ elif value == 2:
+ return "CameraLight"
+
+ return "SceneLight"
+
+
+def lightSerializer(parent, instance, objId, context, depth):
+ return {
+ "parent": getReferenceId(parent),
+ "id": objId,
+ "type": class_name(instance),
+ "properties": {
+ # 'specularColor': instance.GetSpecularColor(),
+ # 'ambientColor': instance.GetAmbientColor(),
+ "switch": instance.GetSwitch(),
+ "intensity": instance.GetIntensity(),
+ "color": instance.GetDiffuseColor(),
+ "position": instance.GetPosition(),
+ "focalPoint": instance.GetFocalPoint(),
+ "positional": instance.GetPositional(),
+ "exponent": instance.GetExponent(),
+ "coneAngle": instance.GetConeAngle(),
+ "attenuationValues": instance.GetAttenuationValues(),
+ "lightType": lightTypeToString(instance.GetLightType()),
+ "shadowAttenuation": instance.GetShadowAttenuation(),
+ },
+ }
+
+
+# -----------------------------------------------------------------------------
+
+
+def renderWindowSerializer(parent, instance, objId, context, depth):
+ dependencies = []
+ rendererIds = []
+
+ rendererCollection = instance.GetRenderers()
+ for rIdx in range(rendererCollection.GetNumberOfItems()):
+ # Grab the next vtkRenderer
+ renderer = rendererCollection.GetItemAsObject(rIdx)
+ rendererId = getReferenceId(renderer)
+ rendererInstance = serializeInstance(
+ instance, renderer, rendererId, context, depth + 1
+ )
+ if rendererInstance:
+ dependencies.append(rendererInstance)
+ rendererIds.append(rendererId)
+
+ calls = context.buildDependencyCallList(
+ objId, rendererIds, "addRenderer", "removeRenderer"
+ )
+
+ return {
+ "parent": getReferenceId(parent),
+ "id": objId,
+ "type": class_name(instance),
+ "properties": {"numberOfLayers": instance.GetNumberOfLayers()},
+ "dependencies": dependencies,
+ "calls": calls,
+ "mtime": instance.GetMTime(),
+ }
--- /dev/null
+r"""
+ This module provides some testing functionality for paraview and
+ vtk web applications. It provides the ability to run an arbitrary
+ test script in a separate thread and communicate the results back
+ to the service so that the CTest framework can be notified of the
+ success or failure of the test.
+
+ This test harness will notice when the test script has finished
+ running and will notify the service to stop. At this point, the
+ test results will be checked in the main thread which ran the
+ service, and in the case of failure an exception will be raised
+ to notify CTest of the failure.
+
+ Test scripts need to follow some simple rules in order to work
+ within the test harness framework:
+
+ 1) implement a function called "runTest(args)", where the args
+ parameter contains all the arguments given to the web application
+ upon starting. Among other important items, args will contain the
+ port number where the web application is listening.
+
+ 2) import the testing module so that the script has access to
+ the functions which indicate success and failure. Also the
+ testing module contains convenience functions that might be of
+ use to the test scripts.
+
+ from vtk.web import testing
+
+ 3) Call the "testPass(testName)" or "testFail(testName)" functions
+ from within the runTest(args) function to indicate to the framework
+ whether the test passed or failed.
+
+"""
+
+import_warning_info = ""
+test_module_comm_queue = None
+
+from vtkmodules.vtkTestingRendering import vtkTesting
+
+# Try standard Python imports
+try:
+ import os, re, time, datetime, threading, imp, inspect, Queue, types, io
+except:
+ import_warning_info += "\nUnable to load at least one basic Python module"
+
+# Image comparison imports
+try:
+ try:
+ from PIL import Image
+ except ImportError:
+ import Image
+ except:
+ raise
+ import base64
+ import itertools
+except:
+ import_warning_info += (
+ "\nUnable to load at least one modules necessary for image comparison"
+ )
+
+# Browser testing imports
+try:
+ import selenium
+ from selenium import webdriver
+except:
+ import_warning_info += (
+ "\nUnable to load at least one module necessary for browser tests"
+ )
+
+# HTTP imports
+try:
+ import requests
+except:
+ import_warning_info += (
+ "\nUnable to load at least one module necessary for HTTP tests"
+ )
+
+
+# Define some infrastructure to support different (or no) browsers
+test_module_browsers = ["firefox", "chrome", "internet_explorer", "safari", "nobrowser"]
+
+
+class TestModuleBrowsers:
+ firefox, chrome, internet_explorer, safari, nobrowser = range(5)
+
+
+# =============================================================================
+# We can use this exception type to indicate that the test shouldn't actually
+# "fail", rather that it was unable to run because some dependencies were not
+# met.
+# =============================================================================
+class DependencyError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+# =============================================================================
+# This class allows usage as a dictionary and an object with named property
+# access.
+# =============================================================================
+class Dictionary(dict):
+ def __getattribute__(self, attrName):
+ return self[attrName]
+
+ def __setattr__(self, attrName, attrValue):
+ self[attrName] = attrValue
+
+
+# =============================================================================
+# Checks whether test script supplied, if so, safely imports needed modules
+# =============================================================================
+def initialize(opts, reactor=None, cleanupMethod=None):
+ """
+ This function should be called to initialize the testing module. The first
+ important thing it does is to store the options for later, since the
+ startTestThread function will need them. Then it checks the arguments that
+ were passed into the server to see if a test was actually requested, making
+ a note of this fact. Then, if a test was required, this function then
+ checks if all the necessary testing modules were safely imported, printing
+ a warning if not. If tests were requested and all modules were present,
+ then this function sets "test_module_do_testing" to True and sets up the
+ startTestThread function to be called after the reactor is running.
+
+ opts: Parsed arguments from the server
+
+ reactor: This argument is optional, but is used by server.py to
+ cause the test thread to be started only after the server itself
+ has started. If it is not provided, the test thread is launched
+ immediately.
+
+ cleanupMethod: A callback method you would like the test thread
+ to execute when the test has finished. This is used by server.py
+ as a way to have the server terminated after the test has finished,
+ but could be used for other cleanup purposes. This argument is
+ also optional.
+ """
+
+ global import_warning_info
+
+ global testModuleOptions
+ testModuleOptions = Dictionary()
+
+ # Copy the testing options into something we can easily extend
+ for arg in vars(opts):
+ optValue = getattr(opts, arg)
+ testModuleOptions[arg] = optValue
+
+ # If we got one, add the cleanup method to the testing options
+ if cleanupMethod:
+ testModuleOptions["cleanupMethod"] = cleanupMethod
+
+ # Check if a test was actually requested
+ if (
+ testModuleOptions.testScriptPath != ""
+ and testModuleOptions.testScriptPath is not None
+ ):
+ # Check if we ran into trouble with any of the testing imports
+ if import_warning_info != "":
+ print("WARNING: Some tests may have unmet dependencies")
+ print(import_warning_info)
+
+ if reactor is not None:
+ # Add startTest callback to the reactor callback queue, so that
+ # the test thread gets started after the reactor is running. Of
+ # course this should only happen if everything is good for tests.
+ reactor.callWhenRunning(_start_test_thread)
+ else:
+ # Otherwise, our aim is to start the thread from another process
+ # so just call the start method.
+ _start_test_thread()
+
+
+# =============================================================================
+# Grab out the command-line arguments needed for by the testing module.
+# =============================================================================
+def add_arguments(parser):
+ """
+ This function retrieves any command-line arguments that the client-side
+ tester needs. In order to run a test, you will typically just need the
+ following:
+
+ --run-test-script => This should be the full path to the test script to
+ be run.
+
+ --baseline-img-dir => This should be the 'Baseline' directory where the
+ baseline images for this test are located.
+
+ --test-use-browser => This should be one of the supported browser types,
+ or else 'nobrowser'. The choices are 'chrome', 'firefox', 'internet_explorer',
+ 'safari', or 'nobrowser'.
+ """
+
+ parser.add_argument(
+ "--run-test-script",
+ default="",
+ help="The path to a test script to run",
+ dest="testScriptPath",
+ )
+
+ parser.add_argument(
+ "--baseline-img-dir",
+ default="",
+ help="The path to the directory containing the web test baseline images",
+ dest="baselineImgDir",
+ )
+
+ parser.add_argument(
+ "--test-use-browser",
+ default="nobrowser",
+ help="One of 'chrome', 'firefox', 'internet_explorer', 'safari', or 'nobrowser'.",
+ dest="useBrowser",
+ )
+
+ parser.add_argument(
+ "--temporary-directory",
+ default=".",
+ help="A temporary directory for storing test images and diffs",
+ dest="tmpDirectory",
+ )
+
+ parser.add_argument(
+ "--test-image-file-name",
+ default="",
+ help="Name of file in which to store generated test image",
+ dest="testImgFile",
+ )
+
+
+# =============================================================================
+# Initialize the test client
+# =============================================================================
+def _start_test_thread():
+ """
+ This function checks whether testing is required and if so, sets up a Queue
+ for the purpose of communicating with the thread. then it starts the
+ after waiting 5 seconds for the server to have a chance to start up.
+ """
+
+ global test_module_comm_queue
+ test_module_comm_queue = Queue.Queue()
+
+ t = threading.Thread(
+ target=launch_web_test,
+ args=[],
+ kwargs={
+ "serverOpts": testModuleOptions,
+ "commQueue": test_module_comm_queue,
+ "testScript": testModuleOptions.testScriptPath,
+ },
+ )
+
+ t.start()
+
+
+# =============================================================================
+# Test scripts call this function to indicate passage of their test
+# =============================================================================
+def test_pass(testName):
+ """
+ Test scripts should call this function to indicate that the test passed. A
+ note is recorded that the test succeeded, and is checked later on from the
+ main thread so that CTest can be notified of this result.
+ """
+
+ global test_module_comm_queue
+ resultObj = {testName: "pass"}
+ test_module_comm_queue.put(resultObj)
+
+
+# =============================================================================
+# Test scripts call this function to indicate failure of their test
+# =============================================================================
+def test_fail(testName):
+ """
+ Test scripts should call this function to indicate that the test failed. A
+ note is recorded that the test did not succeed, and this note is checked
+ later from the main thread so that CTest can be notified of the result.
+
+ The main thread is the only one that can signal test failure in
+ CTest framework, and the main thread won't have a chance to check for
+ passage or failure of the test until the main loop has terminated. So
+ here we just record the failure result, then we check this result in the
+ processTestResults() function, throwing an exception at that point to
+ indicate to CTest that the test failed.
+ """
+
+ global test_module_comm_queue
+ resultObj = {testName: "fail"}
+ test_module_comm_queue.put(resultObj)
+
+
+# =============================================================================
+# Concatenate any number of strings into a single path string.
+# =============================================================================
+def concat_paths(*pathElts):
+ """
+ A very simple convenience function so that test scripts can build platform
+ independent paths out of a list of elements, without having to import the
+ os module.
+
+ pathElts: Any number of strings which should be concatenated together
+ in a platform independent manner.
+ """
+
+ return os.path.join(*pathElts)
+
+
+# =============================================================================
+# So we can change our time format in a single place, this function is
+# provided.
+# =============================================================================
+def get_current_time_string():
+ """
+ This function returns the current time as a string, using ISO 8601 format.
+ """
+
+ return datetime.datetime.now().isoformat(" ")
+
+
+# =============================================================================
+# Uses vtkTesting to compare images. According to comments in the vtkTesting
+# C++ code (and this seems to work), if there are multiple baseline images in
+# the same directory as the baseline_img, and they follow the naming pattern:
+# 'img.png', 'img_1.png', ... , 'img_N.png', then all of these images will be
+# tried for a match.
+# =============================================================================
+def compare_images(test_img, baseline_img, tmp_dir="."):
+ """
+ This function creates a vtkTesting object, and specifies the name of the
+ baseline image file, using a fully qualified path (baseline_img must be
+ fully qualified). Then it calls the vtkTesting method which compares the
+ image (test_img, specified only with a relative path) against the baseline
+ image as well as any other images in the same directory as the baseline
+ image which follow the naming pattern: 'img.png', 'img_1.png', ... , 'img_N.png'
+
+ test_img: File name of output image to be compared against baseline.
+
+ baseline_img: Fully qualified path to first of the baseline images.
+
+ tmp_dir: Fully qualified path to a temporary directory for storing images.
+ """
+
+ # Create a vtkTesting object and specify a baseline image
+ t = vtkTesting()
+ t.AddArgument("-T")
+ t.AddArgument(tmp_dir)
+ t.AddArgument("-V")
+ t.AddArgument(baseline_img)
+
+ # Perform the image comparison test and print out the result.
+ return t.RegressionTest(test_img, 0.05)
+
+
+# =============================================================================
+# Provide a wait function
+# =============================================================================
+def wait_with_timeout(delay=None, limit=0, criterion=None):
+ """
+ This function provides the ability to wait for a certain number of seconds,
+ or else to wait for a specific criterion to be met.
+ """
+ for i in itertools.count():
+ if criterion is not None and criterion():
+ return True
+ elif delay * i > limit:
+ return False
+ else:
+ time.sleep(delay)
+
+
+# =============================================================================
+# Define a WebTest class with five stages of testing: initialization, setup,
+# capture, postprocess, and cleanup.
+# =============================================================================
+class WebTest(object):
+ """
+ This is the base class for all automated web-based tests. It defines five
+ stages that any test must run through, and allows any or all of these
+ stages to be overridden by subclasses. This class defines the run_test
+ method to invoke the five stages overridden by subclasses, one at a time:
+ 1) initialize, 2) setup, 3) capture, 4) postprocess, and 5) cleanup.
+ """
+
+ class Abort:
+ pass
+
+ def __init__(self, url=None, testname=None, **kwargs):
+ self.url = url
+ self.testname = testname
+
+ def run_test(self):
+ try:
+ self.checkdependencies()
+ self.initialize()
+ self.setup()
+ self.capture()
+ self.postprocess()
+ except WebTest.Abort:
+ # Placeholder for future option to return failure result
+ pass
+ except:
+ self.cleanup()
+ raise
+
+ self.cleanup()
+
+ def checkdependencies(self):
+ pass
+
+ def initialize(self):
+ pass
+
+ def setup(self):
+ pass
+
+ def capture(self):
+ pass
+
+ def postprocess(self):
+ pass
+
+ def cleanup(self):
+ pass
+
+
+# =============================================================================
+# Define a WebTest subclass designed specifically for browser-based tests.
+# =============================================================================
+class BrowserBasedWebTest(WebTest):
+ """
+ This class can be used as a base for any browser-based web tests. It
+ introduces the notion of a selenium browser and overrides phases (1) and
+ (3), initialization and cleanup, of the test phases introduced in the base
+ class. Initialization involves selecting the browser type, setting the
+ browser window size, and asking the browser to load the url. Cleanup
+ involves closing the browser window.
+ """
+
+ def __init__(self, size=None, browser=None, **kwargs):
+ self.size = size
+ self.browser = browser
+ self.window = None
+
+ WebTest.__init__(self, **kwargs)
+
+ def initialize(self):
+ try:
+ if self.browser is None or self.browser == TestModuleBrowsers.chrome:
+ self.window = webdriver.Chrome()
+ elif self.browser == TestModuleBrowsers.firefox:
+ self.window = webdriver.Firefox()
+ elif self.browser == TestModuleBrowsers.internet_explorer:
+ self.window = webdriver.Ie()
+ else:
+ raise DependencyError(
+ "self.browser argument has illegal value %r" % (self.browser)
+ )
+ except DependencyError as dErr:
+ raise
+ except Exception as inst:
+ raise DependencyError(inst)
+
+ if self.size is not None:
+ self.window.set_window_size(self.size[0], self.size[1])
+
+ if self.url is not None:
+ self.window.get(self.url)
+
+ def cleanup(self):
+ try:
+ self.window.quit()
+ except:
+ print(
+ "Unable to call window.quit, perhaps this is expected because of unmet browser dependency."
+ )
+
+
+# =============================================================================
+# Extend BrowserBasedWebTest to handle vtk-style image comparison
+# =============================================================================
+class ImageComparatorWebTest(BrowserBasedWebTest):
+ """
+ This class extends browser based web tests to include image comparison. It
+ overrides the capture phase of testing with some functionality to simply
+ grab a screenshot of the entire browser window. It overrides the
+ postprocess phase with a call to vtk image comparison functionality.
+ Derived classes can then simply override the setup function with a series
+ of selenium-based browser interactions to create a complete test. Derived
+ classes may also prefer to override the capture phase to capture only
+ certain portions of the browser window for image comparison.
+ """
+
+ def __init__(self, filename=None, baseline=None, temporaryDir=None, **kwargs):
+ if filename is None:
+ raise TypeError("missing argument 'filename'")
+ if baseline is None:
+ raise TypeError("missing argument 'baseline'")
+
+ BrowserBasedWebTest.__init__(self, **kwargs)
+ self.filename = filename
+ self.baseline = baseline
+ self.tmpDir = temporaryDir
+
+ def capture(self):
+ self.window.save_screenshot(self.filename)
+
+ def postprocess(self):
+ result = compare_images(self.filename, self.baseline, self.tmpDir)
+
+ if result == 1:
+ test_pass(self.testname)
+ else:
+ test_fail(self.testname)
+
+
+# =============================================================================
+# Given a css selector to use in finding the image element, get the element,
+# then base64 decode the "src" attribute and return it.
+# =============================================================================
+def get_image_data(browser, cssSelector):
+ """
+ This function takes a selenium browser and a css selector string and uses
+ them to find the target HTML image element. The desired image element
+ should contain it's image data as a Base64 encoded JPEG image string.
+ The 'src' attribute of the image is read, Base64-decoded, and then
+ returned.
+
+ browser: A selenium browser instance, as created by webdriver.Chrome(),
+ for example.
+
+ cssSelector: A string containing a CSS selector which will be used to
+ find the HTML image element of interest.
+ """
+
+ # Here's maybe a better way to get at that image element
+ imageElt = browser.find_element_by_css_selector(cssSelector)
+
+ # Now get the Base64 image string and decode it into image data
+ base64String = imageElt.get_attribute("src")
+ b64RegEx = re.compile(r"data:image/jpeg;base64,(.+)")
+ b64Matcher = b64RegEx.match(base64String)
+ imgdata = base64.b64decode(b64Matcher.group(1))
+
+ return imgdata
+
+
+# =============================================================================
+# Combines a variation on above function with the write_image_to_disk function.
+# converting jpg to png in the process, if necessary.
+# =============================================================================
+def save_image_data_as_png(browser, cssSelector, imgfilename):
+ """
+ This function takes a selenium browser instance, a css selector string,
+ and a file name. It uses the css selector string to finds the target HTML
+ Image element, which should contain a Base64 encoded JPEG image string,
+ it decodes the string to image data, and then saves the data to the file.
+ The image type of the written file is determined from the extension of the
+ provided filename.
+
+ browser: A selenium browser instance as created by webdriver.Chrome(),
+ for example.
+
+ cssSelector: A string containing a CSS selector which will be used to
+ find the HTML image element of interest.
+
+ imgFilename: The filename to which to save the image. The extension is
+ used to determine the type of image which should be saved.
+ """
+ imageElt = browser.find_element_by_css_selector(cssSelector)
+ base64String = imageElt.get_attribute("src")
+ b64RegEx = re.compile(r"data:image/jpeg;base64,(.+)")
+ b64Matcher = b64RegEx.match(base64String)
+ img = Image.open(io.BytesIO(base64.b64decode(b64Matcher.group(1))))
+ img.save(imgfilename)
+
+
+# =============================================================================
+# Given a decoded image and the full path to a file, write the image to the
+# file.
+# =============================================================================
+def write_image_to_disk(imgData, filePath):
+ """
+ This function takes an image data, as returned by this module's
+ get_image_data() function for example, and writes it out to the file given by
+ the filePath parameter.
+
+ imgData: An image data object
+ filePath: The full path, including the file name and extension, where
+ the image should be written.
+ """
+
+ with open(filePath, "wb") as f:
+ f.write(imgData)
+
+
+# =============================================================================
+# There could be problems if the script file has more than one class defn which
+# is a subclass of vtk.web.testing.WebTest, so we should write some
+# documentation to help people avoid that.
+# =============================================================================
+def instantiate_test_subclass(pathToScript, **kwargs):
+ """
+ This function takes the fully qualified path to a test file, along with
+ any needed keyword arguments, then dynamically loads the file as a module
+ and finds the test class defined inside of it via inspection. It then
+ uses the keyword arguments to instantiate the test class and return the
+ instance.
+
+ pathToScript: Fully qualified path to python file containing defined
+ subclass of one of the test base classes.
+ kwargs: Keyword arguments to be passed to the constructor of the
+ testing subclass.
+ """
+
+ # Load the file as a module
+ moduleName = imp.load_source("dynamicTestModule", pathToScript)
+ instance = None
+
+ # Inspect dynamically loaded module members
+ for name, obj in inspect.getmembers(moduleName):
+ # Looking for classes only
+ if inspect.isclass(obj):
+ instance = obj.__new__(obj)
+ # And only classes defined in the dynamically loaded module
+ if instance.__module__ == "dynamicTestModule":
+ try:
+ instance.__init__(**kwargs)
+ break
+ except Exception as inst:
+ print("Caught exception: " + str(type(inst)))
+ print(inst)
+ raise
+
+ return instance
+
+
+# =============================================================================
+# For testing purposes, define a function which can interact with a running
+# paraview or vtk web application service.
+# =============================================================================
+def launch_web_test(*args, **kwargs):
+ """
+ This function loads a python file as a module (with no package), and then
+ instantiates the class it must contain, and finally executes the run_test()
+ method of the class (which the class may override, but which is defined in
+ both of the testing base classes, WebTest and ImageComparatorBaseClass).
+ After the run_test() method finishes, this function will stop the web
+ server if required. This function expects some keyword arguments will be
+ present in order for it to complete it's task:
+
+ kwargs['serverOpts']: An object containing all the parameters used
+ to start the web service. Some of them will be used in the test script
+ in order perform the test. For example, the port on which the server
+ was started will be required in order to connect to the server.
+
+ kwargs['testScript']: The full path to the python file containing the
+ testing subclass.
+ """
+
+ serverOpts = None
+ testScriptFile = None
+
+ # This is really the thing all test scripts will need: access to all
+ # the options used to start the server process.
+ if "serverOpts" in kwargs:
+ serverOpts = kwargs["serverOpts"]
+ # print 'These are the serverOpts we got: '
+ # print serverOpts
+
+ # Get the full path to the test script
+ if "testScript" in kwargs:
+ testScriptFile = kwargs["testScript"]
+
+ testName = "unknown"
+
+ # Check for a test file (python file)
+ if testScriptFile is None:
+ print("No test script file found, no test script will be run.")
+ test_fail(testName)
+
+ # The test name will be generated from the python script name, so
+ # match and capture a bunch of contiguous characters which are
+ # not '.', '\', or '/', followed immediately by the string '.py'.
+ fnamePattern = re.compile("([^\.\/\\\]+)\.py")
+ fmatch = re.search(fnamePattern, testScriptFile)
+ if fmatch:
+ testName = fmatch.group(1)
+ else:
+ print(
+ "Unable to parse testScriptFile ("
+ + str(testScriptfile)
+ + "), no test will be run"
+ )
+ test_fail(testName)
+
+ # If we successfully got a test name, we are ready to try and run the test
+ if testName != "unknown":
+
+ # Output file and baseline file names are generated from the test name
+ imgFileName = testName + ".png"
+ knownGoodFileName = concat_paths(serverOpts.baselineImgDir, imgFileName)
+ tempDir = serverOpts.tmpDirectory
+ testImgFileName = serverOpts.testImgFile
+
+ testBrowser = test_module_browsers.index(serverOpts.useBrowser)
+
+ # Now try to instantiate and run the test
+ try:
+ testInstance = instantiate_test_subclass(
+ testScriptFile,
+ testname=testName,
+ host=serverOpts.host,
+ port=serverOpts.port,
+ browser=testBrowser,
+ filename=testImgFileName,
+ baseline=knownGoodFileName,
+ temporaryDir=tempDir,
+ )
+
+ # If we were able to instantiate the test, run it, otherwise we
+ # consider it a failure.
+ if testInstance is not None:
+ try:
+ testInstance.run_test()
+ except DependencyError as derr:
+ # TODO: trigger return SKIP_RETURN_CODE when CMake 3 is required
+ print(
+ "Some dependency of this test was not met, allowing it to pass"
+ )
+ test_pass(testName)
+ else:
+ print("Unable to instantiate test instance, failing test")
+ test_fail(testName)
+ return
+
+ except Exception as inst:
+ import sys, traceback
+
+ tb = sys.exc_info()[2]
+ print("Caught an exception while running test script:")
+ print(" " + str(type(inst)))
+ print(" " + str(inst))
+ print(" " + "".join(traceback.format_tb(tb)))
+ test_fail(testName)
+
+ # If we were passed a cleanup method to run after testing, invoke it now
+ if "cleanupMethod" in serverOpts:
+ serverOpts["cleanupMethod"]()
+
+
+# =============================================================================
+# To keep the service module clean, we'll process the test results here, given
+# the test result object we generated in "launch_web_test". It is
+# passed back to this function after the service has completed. Failure of
+# of the test is indicated by raising an exception in here.
+# =============================================================================
+def finalize():
+ """
+ This function checks the module's global test_module_comm_queue variable for a
+ test result. If one is found and the result is 'fail', then this function
+ raises an exception to communicate the failure to the CTest framework.
+
+ In order for a test result to be found in the test_module_comm_queue variable,
+ the test script must have called either the testPass or testFail functions
+ provided by this test module before returning.
+ """
+
+ global test_module_comm_queue
+
+ if test_module_comm_queue is not None:
+ resultObject = test_module_comm_queue.get()
+
+ failedATest = False
+
+ for testName in resultObject:
+ testResult = resultObject[testName]
+ if testResult == "fail":
+ print(" Test -> " + testName + ": " + testResult)
+ failedATest = True
+
+ if failedATest is True:
+ raise Exception(
+ "At least one of the requested tests failed. "
+ + "See detailed output, above, for more information"
+ )
--- /dev/null
+try:
+ import numpy as np
+except ImportError:
+ raise ImportError(
+ "This module depends on the numpy module. Please make\
+sure that it is installed properly."
+ )
+
+import base64
+
+from vtkmodules.util.numpy_support import vtk_to_numpy
+from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
+
+# Numpy to JS TypedArray
+to_js_type = {
+ "int8": "Int8Array",
+ "uint8": "Uint8Array",
+ "int16": "Int16Array",
+ "uint16": "Uint16Array",
+ "int32": "Int32Array",
+ "uint32": "Uint32Array",
+ "int64": "Int32Array",
+ "uint64": "Uint32Array",
+ "float32": "Float32Array",
+ "float64": "Float64Array",
+}
+
+
+def b64_encode_numpy(obj):
+ # Convert 1D numpy arrays with numeric types to memoryviews with
+ # datatype and shape metadata.
+ if len(obj) == 0:
+ return obj.tolist()
+
+ dtype = obj.dtype
+ if dtype.kind == "f":
+ return np_encode(obj)
+ elif dtype.kind == "b":
+ return np_encode(obj, np.uint8)
+ elif dtype.kind in ["u", "i"]:
+ # Try to see if we can downsize the array
+ max_value = np.amax(obj)
+ min_value = np.amin(obj)
+ signed = min_value < 0
+ test_value = max(max_value, -min_value)
+ if signed:
+ if test_value < np.iinfo(np.int8):
+ return np_encode(obj, np.int8)
+ if test_value < np.iinfo(np.int16).max:
+ return np_encode(obj, np.int16)
+ if test_value < np.iinfo(np.int32).max:
+ return np_encode(obj, np.int32)
+ else:
+ if test_value < np.iinfo(np.uint8).max:
+ return np_encode(obj, np.uint8)
+ if test_value < np.iinfo(np.uint16).max:
+ return np_encode(obj, np.uint16)
+ if test_value < np.iinfo(np.uint32).max:
+ return np_encode(obj, np.uint32)
+
+ # Convert all other numpy arrays to lists
+ return obj.tolist()
+
+
+def np_encode(array, np_type=None):
+ if np_type:
+ n_array = array.astype(np_type).ravel(order="C")
+ return {
+ "bvals": base64.b64encode(memoryview(n_array)).decode("utf-8"),
+ "dtype": str(n_array.dtype),
+ "shape": list(array.shape),
+ }
+ return {
+ "bvals": base64.b64encode(memoryview(array.ravel(order="C"))).decode("utf-8"),
+ "dtype": str(array.dtype),
+ "shape": list(array.shape),
+ }
+
+
+def mesh_array(array):
+ if array:
+ return b64_encode_numpy(vtk_to_numpy(array.GetData()))
+
+
+def data_array(data_array, location="PointData", name=None):
+ if data_array:
+ dataRange = data_array.GetRange(-1)
+ nb_comp = data_array.GetNumberOfComponents()
+ values = vtk_to_numpy(data_array)
+ js_types = to_js_type[str(values.dtype)]
+ return {
+ "name": name if name else data_array.GetName(),
+ "values": b64_encode_numpy(values),
+ "numberOfComponents": nb_comp,
+ "type": js_types,
+ "location": location,
+ "dataRange": dataRange,
+ }
+
+
+def field_data(field_data, names, location="PointData"):
+ fields = []
+ for name in names:
+ array = field_data.GetArray(name)
+ js_array = data_array(array, location, name)
+ if js_array:
+ fields.append(js_array)
+
+ return fields
+
+
+def mesh(dataset, field_to_keep=None, point_arrays=None, cell_arrays=None):
+ """Expect any dataset and extract its surface into a dash_vtk.Mesh state property"""
+ if dataset is None:
+ return None
+
+ # Make sure we have a polydata to export
+ polydata = None
+ if dataset.IsA("vtkPolyData"):
+ polydata = dataset
+ else:
+ extractSkinFilter = vtkDataSetSurfaceFilter()
+ extractSkinFilter.SetInputData(dataset)
+ extractSkinFilter.Update()
+ polydata = extractSkinFilter.GetOutput()
+
+ if polydata.GetPoints() is None:
+ return None
+
+ # Extract mesh
+ state = {"mesh": {}}
+
+ points = mesh_array(polydata.GetPoints())
+ if points:
+ state["mesh"]["points"] = points
+
+ verts = mesh_array(polydata.GetVerts())
+ if verts:
+ state["mesh"]["verts"] = verts
+
+ lines = mesh_array(polydata.GetLines())
+ if lines:
+ state["mesh"]["lines"] = lines
+
+ polys = mesh_array(polydata.GetPolys())
+ if polys:
+ state["mesh"]["polys"] = polys
+
+ strips = mesh_array(polydata.GetStrips())
+ if strips:
+ state["mesh"]["strips"] = strips
+
+ # Scalars
+ if field_to_keep is not None:
+ field = None
+ p_array = polydata.GetPointData().GetArray(field_to_keep)
+ c_array = polydata.GetCellData().GetArray(field_to_keep)
+
+ if c_array:
+ field = data_array(c_array, location="CellData", name=field_to_keep)
+
+ if p_array:
+ field = data_array(p_array, location="PointData", name=field_to_keep)
+
+ if field:
+ state.update({"field": field})
+
+ # PointData Fields
+ if point_arrays:
+ point_data = field_data(polydata.GetPointData(), point_arrays, "PointData")
+ if len(point_data):
+ state.update({"pointArrays": point_data})
+
+ # CellData Fields
+ if cell_arrays:
+ cell_data = field_data(polydata.GetCellData(), cell_arrays, "CellData")
+ if len(cell_data):
+ state.update({"cellArrays": cell_data})
+
+ return state
+
+
+def volume(dataset):
+ """Expect a vtkImageData and extract its setting for the dash_vtk.Volume state"""
+ if dataset is None or not dataset.IsA("vtkImageData"):
+ return None
+
+ state = {
+ "image": {
+ "dimensions": dataset.GetDimensions(),
+ "spacing": dataset.GetSpacing(),
+ "origin": dataset.GetOrigin(),
+ },
+ }
+
+ # Capture image orientation if any
+ if hasattr(dataset, "GetDirectionMatrix"):
+ matrix = dataset.GetDirectionMatrix()
+ js_mat = []
+ for j in range(3):
+ for i in range(3):
+ js_mat.append(matrix.GetElement(i, j))
+
+ state["image"]["direction"] = js_mat
+
+ scalars = dataset.GetPointData().GetScalars()
+ field = data_array(scalars, location="PointData")
+ if field:
+ state["field"] = field
+
+ return state
--- /dev/null
+# -*- coding: utf-8 -*-
+"""Activate venv for current interpreter:
+
+Use `from vtk.web import venv` along one of the following
+ - `--venv /path/to/venv/base` argument
+ - environment variable `VTK_VENV=/path/to/venv/base`
+
+This can be used when you must use an existing Python interpreter, not the venv bin/python.
+"""
+import os
+import site
+import sys
+
+VENV_BASE = None
+VENV_LOADED = False
+
+if "--venv" in sys.argv:
+ VENV_BASE = os.path.abspath(sys.argv[sys.argv.index("--venv") + 1])
+
+if os.environ.get("VTK_VENV"):
+ VENV_BASE = os.path.abspath(os.environ.get("VTK_VENV"))
+
+if not VENV_LOADED and VENV_BASE and os.path.exists(VENV_BASE):
+ VENV_LOADED = True
+ # Code inspired by virutal-env::bin/active_this.py
+ bin_dir = os.path.join(VENV_BASE, "bin")
+ os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep))
+ os.environ["VIRTUAL_ENV"] = VENV_BASE
+ prev_length = len(sys.path)
+ python_libs = os.path.join(VENV_BASE, f"lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages")
+ site.addsitedir(python_libs)
+ sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
+ sys.real_prefix = sys.prefix
+ sys.prefix = VENV_BASE
+ #
+ print(f"VTK is using venv: {VENV_BASE}")
--- /dev/null
+import base64
+import json
+import re
+import os
+import shutil
+import sys
+import zipfile
+
+try:
+ import zlib
+
+ compression = zipfile.ZIP_DEFLATED
+except:
+ compression = zipfile.ZIP_STORED
+
+# -----------------------------------------------------------------------------
+
+
+def convertDirectoryToZipFile(directoryPath):
+ if os.path.isfile(directoryPath):
+ return
+
+ zipFilePath = "%s.zip" % directoryPath
+ zf = zipfile.ZipFile(zipFilePath, mode="w")
+
+ try:
+ for dirName, subdirList, fileList in os.walk(directoryPath):
+ for fname in fileList:
+ fullPath = os.path.join(dirName, fname)
+ relPath = "%s" % (os.path.relpath(fullPath, directoryPath))
+ zf.write(fullPath, arcname=relPath, compress_type=compression)
+ finally:
+ zf.close()
+
+ shutil.rmtree(directoryPath)
+ shutil.move(zipFilePath, directoryPath)
+
+
+# -----------------------------------------------------------------------------
+
+
+def addDataToViewer(dataPath, srcHtmlPath, disableGirder=False):
+ if os.path.isfile(dataPath) and os.path.exists(srcHtmlPath):
+ dstDir = os.path.dirname(dataPath)
+ dstHtmlPath = os.path.join(dstDir, "%s.html" % os.path.basename(dataPath)[:-6])
+
+ # Extract data as base64
+ with open(dataPath, "rb") as data:
+ dataContent = data.read()
+ base64Content = base64.b64encode(dataContent)
+ base64Content = base64Content.decode().replace("\n", "")
+
+ # Create new output file
+ with open(srcHtmlPath, mode="r", encoding="utf-8") as srcHtml:
+ with open(dstHtmlPath, mode="w", encoding="utf-8") as dstHtml:
+ for line in srcHtml:
+ if disableGirder and "</title>" in line:
+ dstHtml.write(
+ """
+ <script>
+ // Force reloading the page if we want to disable girder before anything else.
+ const urlParams = new URLSearchParams(window.location.search);
+ if (urlParams.get('noGirder') != 'true') {
+ urlParams.set('noGirder', 'true');
+ window.location.search = urlParams;
+ }
+ </script>
+ """
+ )
+ if "</body>" in line:
+ dstHtml.write("<script>\n")
+ dstHtml.write('var contentToLoad = "%s";\n\n' % base64Content)
+ dstHtml.write(
+ 'Glance.importBase64Dataset("%s" , contentToLoad, glanceInstance.proxyManager);\n'
+ % os.path.basename(dataPath)
+ )
+ dstHtml.write("glanceInstance.showApp();\n")
+ dstHtml.write("</script>\n")
+
+ dstHtml.write(line)
+
+
+# -----------------------------------------------------------------------------
+
+
+def numericSorted(l):
+ """Numerically sort a list of strings."""
+
+ # pattern to split name into numeric and non-numeric parts
+ splitter_pattern = re.compile('([0-9]+|[^0-9]+)')
+
+ def keyfunc(name):
+ """Sorting key for numeric sorting."""
+ split_name = re.findall(splitter_pattern, name)
+ # one-liner to convert numeric parts into integers
+ split_name = list(map(lambda x: int(x) if x.isdigit() else x, split_name))
+ # ensure that list begins with a string to avoid string<->int compare
+ if split_name and isinstance(split_name[0], int):
+ split_name.insert(0, '')
+ return split_name
+
+ # return the numerically sorted list
+ return sorted(l, key=keyfunc)
+
+
+# -----------------------------------------------------------------------------
+
+
+def zipAllTimeSteps(directoryPath):
+ if os.path.isfile(directoryPath):
+ return
+
+ class UrlCounterDict(dict):
+ Counter = 0
+
+ def GetUrlName(self, name):
+ if name not in self.keys():
+ self[name] = str(objNameToUrls.Counter)
+ self.Counter = self.Counter + 1
+ return self[name]
+
+ def InitIndex(sourcePath, destObj):
+ with open(sourcePath, "r") as sourceFile:
+ sourceData = sourceFile.read()
+ sourceObj = json.loads(sourceData)
+ for key in sourceObj:
+ destObj[key] = sourceObj[key]
+ # remove vtkHttpDataSetReader information
+ for obj in destObj["scene"]:
+ obj.pop(obj["type"])
+ obj.pop("type")
+
+ def getUrlToNameDictionary(indexObj):
+ urls = {}
+ for obj in indexObj["scene"]:
+ urls[obj[obj["type"]]["url"]] = obj["name"]
+ return urls
+
+ def addDirectoryToZip(
+ dirname, zipobj, storedData, rootIdx, timeStep, objNameToUrls
+ ):
+ # Update root index.json file from index.json of this timestep
+ with open(os.path.join(dirname, "index.json"), "r") as currentIdxFile:
+ currentIdx = json.loads(currentIdxFile.read())
+ urlToName = getUrlToNameDictionary(currentIdx)
+ rootTimeStepSection = rootIdx["animation"]["timeSteps"][timeStep]
+ for key in currentIdx:
+ if key == "scene" or key == "version":
+ continue
+ rootTimeStepSection[key] = currentIdx[key]
+ for obj in currentIdx["scene"]:
+ objName = obj["name"]
+ rootTimeStepSection[objName] = {}
+ rootTimeStepSection[objName]["actor"] = obj["actor"]
+ rootTimeStepSection[objName]["actorRotation"] = obj["actorRotation"]
+ rootTimeStepSection[objName]["mapper"] = obj["mapper"]
+ rootTimeStepSection[objName]["property"] = obj["property"]
+
+ # For every object in the current timestep
+ for folder in sorted(os.listdir(dirname)):
+ currentItem = os.path.join(dirname, folder)
+ if os.path.isdir(currentItem) is False:
+ continue
+ # Write all data array of the current timestep in the archive
+ for filename in os.listdir(os.path.join(currentItem, "data")):
+ fullpath = os.path.join(currentItem, "data", filename)
+ if os.path.isfile(fullpath) and filename not in storedData:
+ storedData.add(filename)
+ relPath = os.path.join("data", filename)
+ zipobj.write(fullpath, arcname=relPath, compress_type=compression)
+ # Write the index.json containing pointers to these data arrays
+ # while replacing every basepath as '../../data'
+ objIndexFilePath = os.path.join(dirname, folder, "index.json")
+ with open(objIndexFilePath, "r") as objIndexFile:
+ objIndexObjData = json.loads(objIndexFile.read())
+ for elm in objIndexObjData.keys():
+ try:
+ if "ref" in objIndexObjData[elm].keys():
+ objIndexObjData[elm]["ref"]["basepath"] = "../../data"
+ if "arrays" in objIndexObjData[elm].keys():
+ for array in objIndexObjData[elm]["arrays"]:
+ array["data"]["ref"]["basepath"] = "../../data"
+ except AttributeError:
+ continue
+ currentObjName = urlToName[folder]
+ objIndexRelPath = os.path.join(
+ objNameToUrls.GetUrlName(currentObjName), str(timeStep), "index.json"
+ )
+ zipobj.writestr(
+ objIndexRelPath,
+ json.dumps(objIndexObjData, indent=2),
+ compress_type=compression,
+ )
+
+ # ---
+
+ zipFilePath = "%s.zip" % directoryPath
+ currentDirectory = os.path.abspath(os.path.join(directoryPath, os.pardir))
+ rootIndexPath = os.path.join(currentDirectory, "index.json")
+ rootIndexFile = open(rootIndexPath, "r")
+ rootIndexObj = json.loads(rootIndexFile.read())
+
+ zf = zipfile.ZipFile(zipFilePath, mode="w")
+ try:
+ # We copy the scene from an index of a specific timestep to the root index
+ # Scenes should all have the same objects so only do it for the first one
+ isSceneInitialized = False
+ # currentlyAddedData set stores hashes of every data we already added to the
+ # vtkjs archive to prevent data duplication
+ currentlyAddedData = set()
+ # Regex that folders storing timestep data from paraview should follow
+ reg = re.compile(r"^" + os.path.basename(directoryPath) + r"\.[0-9]+$")
+ # We assume an object will not be deleted from a timestep to another so we create a generic index.json for each object
+ genericIndexObj = {}
+ genericIndexObj["series"] = []
+ timeStep = 0
+ for item in rootIndexObj["animation"]["timeSteps"]:
+ genericIndexObj["series"].append({})
+ genericIndexObj["series"][timeStep]["url"] = str(timeStep)
+ genericIndexObj["series"][timeStep]["timeStep"] = float(item["time"])
+ timeStep = timeStep + 1
+ # Keep track of the url for every object
+ objNameToUrls = UrlCounterDict()
+
+ timeStep = 0
+ # zip all timestep directories
+ for folder in numericSorted(os.listdir(currentDirectory)):
+ fullPath = os.path.join(currentDirectory, folder)
+ if os.path.isdir(fullPath) and reg.match(folder):
+ if not isSceneInitialized:
+ InitIndex(os.path.join(fullPath, "index.json"), rootIndexObj)
+ isSceneInitialized = True
+ addDirectoryToZip(
+ fullPath,
+ zf,
+ currentlyAddedData,
+ rootIndexObj,
+ timeStep,
+ objNameToUrls,
+ )
+ shutil.rmtree(fullPath)
+ timeStep = timeStep + 1
+
+ # Write every index.json holding time information for each object
+ for name in objNameToUrls:
+ zf.writestr(
+ os.path.join(objNameToUrls[name], "index.json"),
+ json.dumps(genericIndexObj, indent=2),
+ compress_type=compression,
+ )
+
+ # Update root index.json urls and write it in the archive
+ for obj in rootIndexObj["scene"]:
+ obj["id"] = obj["name"]
+ obj["type"] = "vtkHttpDataSetSeriesReader"
+ obj["vtkHttpDataSetSeriesReader"] = {}
+ obj["vtkHttpDataSetSeriesReader"]["url"] = objNameToUrls[obj["name"]]
+ zf.writestr(
+ "index.json", json.dumps(rootIndexObj, indent=2), compress_type=compression
+ )
+ os.remove(rootIndexPath)
+
+ finally:
+ zf.close()
+
+ shutil.move(zipFilePath, directoryPath)
+
+
+# -----------------------------------------------------------------------------
+# Main
+# -----------------------------------------------------------------------------
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print(
+ "Usage: directoryToFile /path/to/directory.vtkjs [/path/to/ParaViewGlance.html]"
+ )
+ else:
+ fileName = sys.argv[1]
+ convertDirectoryToZipFile(fileName)
+
+ if len(sys.argv) == 3:
+ addDataToViewer(fileName, sys.argv[2])
--- /dev/null
+r"""wslink is a module that extends any
+wslink related classes for the purposes of vtkWeb.
+
+"""
+
+from __future__ import absolute_import, division, print_function
+
+# import inspect, types, string, random, logging, six, json, re, base64
+import json, base64, logging, time
+
+from vtkmodules.web.errors import WebDependencyMissingError
+
+try:
+ from wslink import websocket
+ from wslink import register as exportRpc
+except ImportError:
+ raise WebDependencyMissingError()
+
+from vtkmodules.web import protocols
+from vtkmodules.vtkWebCore import vtkWebApplication
+
+# =============================================================================
+application = None
+
+# =============================================================================
+#
+# Base class for vtkWeb ServerProtocol
+#
+# =============================================================================
+
+
+class ServerProtocol(websocket.ServerProtocol):
+ """
+ Defines the core server protocol for vtkWeb. Adds support to
+ marshall/unmarshall RPC callbacks that involve ServerManager proxies as
+ arguments or return values.
+
+ Applications typically don't use this class directly, but instead
+ sub-class it and call self.registerVtkWebProtocol() with useful vtkWebProtocols.
+ """
+
+ def __init__(self):
+ logging.info("Creating SP")
+ self.setSharedObject("app", self.initApplication())
+ websocket.ServerProtocol.__init__(self)
+
+ def initApplication(self):
+ """
+ Let subclass optionally initialize a custom application in lieu
+ of the default vtkWebApplication.
+ """
+ global application
+ if not application:
+ application = vtkWebApplication()
+ return application
+
+ def setApplication(self, application):
+ self.setSharedObject("app", application)
+
+ def getApplication(self):
+ return self.getSharedObject("app")
+
+ def registerVtkWebProtocol(self, protocol):
+ self.registerLinkProtocol(protocol)
+
+ def getVtkWebProtocols(self):
+ return self.getLinkProtocols()
--- /dev/null
+if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+ message(FATAL_ERROR
+ "The VTK::WebAssembly module requires Emscripten compiler.")
+endif ()
+
+set(classes
+ vtkWasmSceneManager)
+
+vtk_module_add_module(VTK::WebAssembly
+ CLASSES ${classes})
+
+vtk_add_test_mangling(VTK::WebAssembly)
+
+set(_vtk_wasm_scene_manager_autoinit_mods)
+get_property(_vtk_wasm_scene_manager_optional_deps GLOBAL
+ PROPERTY "_vtk_module_VTK::WebAssembly_optional_depends")
+foreach(_module IN LISTS _vtk_wasm_scene_manager_private_deps _vtk_wasm_scene_manager_optional_deps)
+ if (NOT TARGET "${_module}")
+ continue ()
+ endif ()
+ list(APPEND _vtk_wasm_scene_manager_autoinit_mods "${_module}")
+endforeach()
+vtk_module_autoinit(
+ TARGETS WebAssembly
+ MODULES ${_vtk_wasm_scene_manager_autoinit_mods})
+# -----------------------------------------------------------------------------
+# Emscripten compile+link options
+# -----------------------------------------------------------------------------
+set(emscripten_link_options)
+list(APPEND emscripten_link_options
+ "-lembind"
+ "--extern-post-js=${CMAKE_CURRENT_SOURCE_DIR}/post.js"
+ # "--embind-emit-tsd=vtkWasmSceneManager.ts"
+ #"--memoryprofiler"
+ #"--cpuprofiler"
+ "-sALLOW_MEMORY_GROWTH=1"
+ "-sALLOW_TABLE_GROWTH=1"
+ "-sEXPORT_NAME=vtkWasmSceneManager"
+ "-sENVIRONMENT=node,web"
+ "-sEXPORTED_RUNTIME_METHODS=['addFunction','UTF8ToString','FS']"
+ # "-sEXCEPTION_DEBUG=1" # prints stack trace for uncaught C++ exceptions from VTK (very rare, but PITA to figure out)
+ # "-sGL_DEBUG=1"
+ # "-sGL_ASSERTIONS=1"
+ # "-sTRACE_WEBGL_CALLS=1"
+ )
+if (CMAKE_SIZEOF_VOID_P EQUAL "8")
+ list(APPEND emscripten_link_options
+ "-sMAXIMUM_MEMORY=16GB")
+else ()
+ list(APPEND emscripten_link_options
+ "-sMAXIMUM_MEMORY=4GB")
+endif ()
+# -----------------------------------------------------------------------------
+# Optimizations
+# -----------------------------------------------------------------------------
+set(emscripten_optimizations)
+set(emscripten_debug_options)
+if (CMAKE_BUILD_TYPE STREQUAL "Release")
+ set(vtk_scene_manager_wasm_optimize "BEST")
+ set(vtk_scene_manager_wasm_debuginfo "NONE")
+elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
+ set(vtk_scene_manager_wasm_optimize "SMALLEST_WITH_CLOSURE")
+ set(vtk_scene_manager_wasm_debuginfo "NONE")
+elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
+ set(vtk_scene_manager_wasm_optimize "MORE")
+ set(vtk_scene_manager_wasm_debuginfo "PROFILE")
+elseif (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(vtk_scene_manager_wasm_optimize "NO_OPTIMIZATION")
+ set(vtk_scene_manager_wasm_debuginfo "DEBUG_NATIVE")
+endif ()
+set(vtk_scene_manager_wasm_optimize_NO_OPTIMIZATION "-O0")
+set(vtk_scene_manager_wasm_optimize_LITTLE "-O1")
+set(vtk_scene_manager_wasm_optimize_MORE "-O2")
+set(vtk_scene_manager_wasm_optimize_BEST "-O3")
+set(vtk_scene_manager_wasm_optimize_SMALLEST "-Os")
+set(vtk_scene_manager_wasm_optimize_SMALLEST_WITH_CLOSURE "-Oz")
+set(vtk_scene_manager_wasm_optimize_SMALLEST_WITH_CLOSURE_link "--closure=1")
+
+if (DEFINED "vtk_scene_manager_wasm_optimize_${vtk_scene_manager_wasm_optimize}")
+ list(APPEND emscripten_optimizations
+ ${vtk_scene_manager_wasm_optimize_${vtk_scene_manager_wasm_optimize}})
+ list(APPEND emscripten_link_options
+ ${vtk_scene_manager_wasm_optimize_${vtk_scene_manager_wasm_optimize}_link})
+else ()
+ message (FATAL_ERROR "Unrecognized value for vtk_scene_manager_wasm_optimize=${vtk_scene_manager_wasm_optimize}")
+endif ()
+
+set(vtk_scene_manager_wasm_debuginfo_NONE "-g0")
+set(vtk_scene_manager_wasm_debuginfo_READABLE_JS "-g1")
+set(vtk_scene_manager_wasm_debuginfo_PROFILE "-g2")
+set(vtk_scene_manager_wasm_debuginfo_DEBUG_NATIVE "-g3")
+set(vtk_scene_manager_wasm_debuginfo_DEBUG_NATIVE_link "-sASSERTIONS=1")
+if (DEFINED "vtk_scene_manager_wasm_debuginfo_${vtk_scene_manager_wasm_debuginfo}")
+ list(APPEND emscripten_debug_options
+ ${vtk_scene_manager_wasm_debuginfo_${vtk_scene_manager_wasm_debuginfo}})
+ list(APPEND emscripten_link_options
+ ${vtk_scene_manager_wasm_debuginfo_${vtk_scene_manager_wasm_debuginfo}_link})
+else ()
+ message (FATAL_ERROR "Unrecognized value for vtk_scene_manager_wasm_debuginfo=${vtk_scene_manager_wasm_debuginfo}")
+endif ()
+
+vtk_module_add_executable(WasmSceneManager
+ BASENAME vtkWasmSceneManager
+ vtkWasmSceneManagerEmBinding.cxx)
+add_executable("VTK::WasmSceneManager" ALIAS
+ WasmSceneManager)
+target_link_libraries(WasmSceneManager
+ PRIVATE
+ VTK::WebAssembly)
+target_compile_options(WasmSceneManager
+ PRIVATE
+ ${emscripten_compile_options}
+ ${emscripten_optimizations}
+ ${emscripten_debug_options})
+target_link_options(WasmSceneManager
+ PRIVATE
+ ${emscripten_link_options}
+ ${emscripten_optimizations}
+ ${emscripten_debug_options})
+set_target_properties(WasmSceneManager
+ PROPERTIES
+ OUTPUT_NAME "vtkWasmSceneManager"
+ SUFFIX ".mjs")
+# [cmake/cmake#20745](https://gitlab.kitware.com/cmake/cmake/-/issues/20745)
+# CMake doesn't install multiple files associated with an executable target.
+install(FILES
+ "$<TARGET_FILE_DIR:WasmSceneManager>/vtkWasmSceneManager.wasm"
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
--- /dev/null
+vtk_module_test_data(
+ Data/WasmSceneManager/scalar-bar-widget.blobs.json
+ Data/WasmSceneManager/scalar-bar-widget.states.json
+ Data/WasmSceneManager/simple.blobs.json)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+ add_subdirectory(JavaScript)
+endif ()
--- /dev/null
+set(vtk_nodejs_min_version "23.8.0")
+find_package(NodeJS "${vtk_nodejs_min_version}" REQUIRED)
+if (VTK_WEBASSEMBLY_64_BIT)
+ set(_vtk_node_args "--experimental-wasm-memory64")
+endif ()
+set(_vtk_testing_nodejs_exe "${NodeJS_INTERPRETER}")
+
+if (CMAKE_HOST_WIN32)
+ list(APPEND _vtk_node_args
+ --import "file://$<TARGET_FILE:VTK::WasmSceneManager>")
+else ()
+ list(APPEND _vtk_node_args
+ --import "$<TARGET_FILE:VTK::WasmSceneManager>")
+endif ()
+vtk_add_test_module_javascript_node(
+ testBindRenderWindow.mjs
+ testBlobs.mjs
+ testInitialize.mjs,NO_DATA
+ testInvoke.mjs
+ testOSMesaRenderWindowPatch.mjs
+ testSkipProperty.mjs
+ testStates.mjs)
--- /dev/null
+async function testBindRenderWindow() {
+ const manager = await globalThis.createVTKWasmSceneManager({});
+ manager.initialize();
+ manager.registerStateJSON(
+ {
+ Id: 1,
+ ClassName: "vtkCocoaRenderWindow",
+ SuperClassNames: ["vtkRenderWindow"],
+ Interactor: { Id: 2 },
+ "vtk-object-manager-kept-alive": true,
+ });
+ manager.registerStateJSON(
+ {
+ Id: 2,
+ ClassName: "vtkCocoaRenderWindowInteractor",
+ SuperClassNames: ["vtkRenderWindowInteractor"],
+ RenderWindow: { Id: 1 },
+ });
+ manager.updateObjectsFromStates();
+
+ manager.bindRenderWindow(1, "#my-canvas-id");
+
+ manager.updateStateFromObject(1);
+ if (manager.getState(1).CanvasSelector !== "#my-canvas-id") {
+ throw new Error("CanvasSelector was not set correctly on RenderWindow.");
+ }
+
+ manager.updateStateFromObject(2);
+ if (manager.getState(2).CanvasSelector !== "#my-canvas-id") {
+ throw new Error("CanvasSelector was not set correctly on RenderWindowInteractor.");
+ }
+}
+const tests = [
+ {
+ description: "Bind RenderWindow to Canvas",
+ test: testBindRenderWindow,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+import { readFile } from "fs/promises";
+import path from "path";
+
+async function testBlobs() {
+ const dataDirectoryIndex = process.argv.indexOf("-D") + 1;
+ if (dataDirectoryIndex <= 0) {
+ throw new Error("Please provide path to a blobs file using -D");
+ }
+ const dataDirectory = process.argv[dataDirectoryIndex];
+ const blobs = JSON.parse(await readFile(path.join(dataDirectory, "Data", "WasmSceneManager", "simple.blobs.json")));
+ const manager = await globalThis.createVTKWasmSceneManager({})
+ if (!manager.initialize()) {
+ throw new Error("Failed to initialize scene manager");
+ }
+
+ for (let hash in blobs) {
+ if (!manager.registerBlob(hash, new Uint8Array(blobs[hash].bytes))) {
+ throw new Error(`Failed to register blob with hash=${hash}`);
+ }
+ }
+ for (let hash in blobs) {
+ const blob = manager.getBlob(hash);
+ if (!(blob instanceof Uint8Array)) {
+ throw new Error(`getBlob did not return a Uint8Array for hash=${hash}`);
+ }
+ if (blob.toString() !== blobs[hash].bytes.toString()) {
+ throw new Error(`blob for hash=${hash} does not match registered blob.`);
+ }
+ }
+}
+
+const tests = [
+ {
+ description: "Register blobs with hashes",
+ test: testBlobs,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+async function testInitialize() {
+ const manager = await globalThis.createVTKWasmSceneManager({});
+ if (!manager.initialize()) {
+ throw new Error();
+ }
+}
+const tests = [
+ {
+ description: "Initialize VTK scene manager",
+ test: testInitialize,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+async function testInvoke() {
+ const manager = await globalThis.createVTKWasmSceneManager({});
+ manager.initialize();
+ // manager.setDeserializerLogVerbosity("INFO");
+ // manager.setObjectManagerLogVerbosity("INFO");
+ // manager.setInvokerLogVerbosity("INFO");
+ manager.registerStateJSON({
+ "ClassName": "vtkCamera", "SuperClassNames": ["vtkObject"], "vtk-object-manager-kept-alive": true, "Id": 1
+ });
+
+ manager.updateObjectsFromStates();
+
+ manager.updateStateFromObject(1);
+ let state = manager.getState(1);
+ if (JSON.stringify(state.Position) != JSON.stringify([0, 0, 1])) {
+ throw new Error("Failed to initialize camera state");
+ }
+
+ // Invoke a method named "Elevation" on the camera with argument 10.0
+ manager.invoke(1, "Elevation", [10.0]);
+
+ manager.updateStateFromObject(1);
+ state = manager.getState(1);
+ if (JSON.stringify(state.Position) != JSON.stringify([0, 0.17364817766693033, 0.9848077530122081])) {
+ throw new Error("vtkCamera::Elevation(10) did not work!");
+ }
+}
+
+const tests = [
+ {
+ description: "Invoke methods",
+ test: testInvoke,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+async function testOSMesaRenderWindowPatch() {
+ const manager = await globalThis.createVTKWasmSceneManager({});
+ manager.initialize();
+ manager.registerStateJSON({
+ Id: 1,
+ ClassName: "vtkOSOpenGLRenderWindow",
+ SuperClassNames: ["vtkWindow", "vtkRenderWindow"],
+ "vtk-object-manager-kept-alive": true,
+ });
+ if (manager.getState(1).ClassName !== "vtkWebAssemblyOpenGLRenderWindow") {
+ throw new Error("RenderWindow state was not created as vtkWebAssemblyOpenGLRenderWindow.");
+ }
+ manager.updateObjectsFromStates();
+
+ manager.updateObjectFromStateJSON({
+ Id: 1,
+ ClassName: "vtkOSOpenGLRenderWindow",
+ SuperClassNames: ["vtkWindow", "vtkRenderWindow"],
+ "vtk-object-manager-kept-alive": true,
+ });
+ if (manager.getState(1).ClassName !== "vtkWebAssemblyOpenGLRenderWindow") {
+ throw new Error("RenderWindow state was not updated as vtkWebAssemblyOpenGLRenderWindow.");
+ }
+}
+const tests = [
+ {
+ description: "Patch vtkOSOpenGLRenderWindow to vtkWebAssemblyOpenGLRenderWindow",
+ test: testOSMesaRenderWindowPatch,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+async function testSkipProperty() {
+ const manager = await globalThis.createVTKWasmSceneManager({});
+ manager.initialize();
+ manager.registerStateJSON({
+ "ClassName": "vtkCamera", "SuperClassNames": ["vtkObject"], "vtk-object-manager-kept-alive": true, "Id": 1
+ });
+
+ manager.updateObjectsFromStates();
+
+ // Skip Position and update object.
+ manager.skipProperty("vtkOpenGLCamera", "Position");
+ manager.updateObjectFromStateJSON({
+ "Id": 1,
+ "Position": [0, 1, 2]
+ });
+
+ // Verify property was skipped
+ manager.updateStateFromObject(1);
+ let state = manager.getState(1);
+ if (JSON.stringify(state.Position) == JSON.stringify([0, 1, 2])) {
+ throw new Error("vtkCamera::Position did not get skipped!");
+ }
+
+ // UnSkip Position and update object.
+ manager.unSkipProperty("vtkOpenGLCamera", "Position");
+ manager.updateObjectFromStateJSON({
+ "Id": 1,
+ "Position": [3, 4, 5]
+ });
+
+ // Verify property was deserialized
+ manager.updateStateFromObject(1);
+ state = manager.getState(1);
+ if (JSON.stringify(state.Position) != JSON.stringify([3, 4, 5])) {
+ throw new Error("vtkCamera::Position did not get unskipped!");
+ }
+}
+
+const tests = [
+ {
+ description: "Invoke methods",
+ test: testSkipProperty,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+import { readFile } from "fs/promises";
+import path from "path";
+
+const object_ids = [1, 2, 3, 41, 5, 42, 44, 4, 6, 33, 35, 38, 40, 43, 11, 45, 46, 47, 48, 49, 50, 51, 7, 34, 36, 37, 39, 12, 8, 9, 10, 13, 14, 15, 16, 19, 21, 24, 27, 30, 17, 18, 20, 22, 23, 25, 26, 28, 29, 31, 32]
+const exepected_dependencies = [1, 2, 3, 41, 5, 42, 44, 4, 6, 33, 35, 38, 40, 43, 11, 45, 46, 47, 48, 49, 50, 51, 7, 34, 36, 37, 39, 12, 8, 9, 10, 13, 14, 15, 16, 19, 21, 24, 27, 30, 17, 18, 20, 22, 23, 25, 26, 28, 29]
+
+async function testStates() {
+ const dataDirectoryIndex = process.argv.indexOf("-D") + 1;
+ if (dataDirectoryIndex <= 0) {
+ throw new Error("Please provide path to a blobs file using -D");
+ }
+ const dataDirectory = process.argv[dataDirectoryIndex];
+ const blobs = JSON.parse(await readFile(path.join(dataDirectory, "Data", "WasmSceneManager", "scalar-bar-widget.blobs.json")));
+ const states = JSON.parse(await readFile(path.join(dataDirectory, "Data", "WasmSceneManager", "scalar-bar-widget.states.json")));
+ const manager = await globalThis.createVTKWasmSceneManager({});
+ if (!manager.initialize()) {
+ throw new Error("Failed to initialize scene manager");
+ }
+ for (let i = 0; i < object_ids.length; ++i) {
+ const object_id = object_ids[i];
+ if (!manager.registerState(JSON.stringify(states[object_id]))) {
+ throw new Error(`Failed to register state at object_id=${object_id}`);
+ }
+ }
+ for (let hash in blobs) {
+ if (!manager.registerBlob(hash, new Uint8Array(blobs[hash].bytes))) {
+ throw new Error(`Failed to register blob with hash=${hash}`);
+ }
+ }
+ manager.updateObjectsFromStates();
+ const activeIds = manager.getAllDependencies(0);
+ if (!(activeIds instanceof Uint32Array)) {
+ throw new Error("getAllDependencies did not return a Uint32Array");
+ }
+ if (activeIds.toString() != exepected_dependencies.toString()) {
+ throw new Error(`${activeIds} != ${exepected_dependencies}`);
+ }
+}
+
+const tests = [
+ {
+ description: "Register states",
+ test: testStates,
+ },
+];
+
+let exitCode = 0;
+for (let test of tests) {
+ try {
+ await test.test();
+ console.log("✓", test.description);
+ exitCode |= 0;
+ }
+ catch (error) {
+ console.log("x", test.description);
+ console.log(error);
+ exitCode |= 1;
+ }
+}
+process.exit(exitCode);
--- /dev/null
+globalThis.createVTKWasmSceneManager = vtkWasmSceneManager;
--- /dev/null
+NAME
+ VTK::WebAssembly
+LIBRARY_NAME
+ vtkWebAssembly
+SPDX_LICENSE_IDENTIFIER
+ BSD-3-Clause
+SPDX_COPYRIGHT_TEXT
+ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+DEPENDS
+ VTK::SerializationManager
+PRIVATE_DEPENDS
+ VTK::RenderingCore
+OPTIONAL_DEPENDS
+ VTK::RenderingContextOpenGL2
+ VTK::RenderingOpenGL2
+ VTK::RenderingUI
+ VTK::RenderingVolumeOpenGL2
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkWasmSceneManager.h"
+
+#include "vtkCallbackCommand.h"
+#include "vtkCommand.h"
+#include "vtkObjectFactory.h"
+
+#include "vtkRenderWindow.h"
+#include "vtkRenderWindowInteractor.h"
+#include "vtkRenderer.h"
+#include "vtkWebAssemblyRenderWindowInteractor.h"
+
+// Init factories.
+#ifdef VTK_MODULE_ENABLE_VTK_RenderingContextOpenGL2
+#include "vtkRenderingContextOpenGL2Module.h"
+#endif
+#ifdef VTK_MODULE_ENABLE_VTK_RenderingOpenGL2
+#include "vtkOpenGLPolyDataMapper.h" // needed to remove unused mapper, also includes vtkRenderingOpenGL2Module.h
+#include "vtkWebAssemblyOpenGLRenderWindow.h"
+#endif
+#ifdef VTK_MODULE_ENABLE_VTK_RenderingUI
+#include "vtkRenderingUIModule.h"
+#endif
+#ifdef VTK_MODULE_ENABLE_VTK_RenderingVolumeOpenGL2
+#include "vtkRenderingVolumeOpenGL2Module.h"
+#endif
+
+VTK_ABI_NAMESPACE_BEGIN
+//-------------------------------------------------------------------------------
+vtkStandardNewMacro(vtkWasmSceneManager);
+
+//-------------------------------------------------------------------------------
+vtkWasmSceneManager::vtkWasmSceneManager() = default;
+
+//-------------------------------------------------------------------------------
+vtkWasmSceneManager::~vtkWasmSceneManager() = default;
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::Initialize()
+{
+ bool result = this->Superclass::Initialize();
+#ifdef VTK_MODULE_ENABLE_VTK_RenderingOpenGL2
+ // Remove the default vtkOpenGLPolyDataMapper as it is not used with wasm build.
+ /// get rid of serialization handler
+ this->Serializer->UnRegisterHandler(typeid(vtkOpenGLPolyDataMapper));
+ /// get rid of de-serialization handler
+ this->Deserializer->UnRegisterHandler(typeid(vtkOpenGLPolyDataMapper));
+ /// get rid of constructor
+ this->Deserializer->UnRegisterConstructor("vtkOpenGLPolyDataMapper");
+#endif
+ return result;
+}
+
+//-------------------------------------------------------------------------------
+void vtkWasmSceneManager::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->vtkObjectManager::PrintSelf(os, indent);
+}
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::SetSize(vtkTypeUInt32 identifier, int width, int height)
+{
+ auto object = this->GetObjectAtId(identifier);
+ if (auto renderWindow = vtkRenderWindow::SafeDownCast(object))
+ {
+ if (auto iren = renderWindow->GetInteractor())
+ {
+ iren->UpdateSize(width, height);
+ return true;
+ }
+ }
+ return false;
+}
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::Render(vtkTypeUInt32 identifier)
+{
+ auto object = this->GetObjectAtId(identifier);
+ if (auto renderWindow = vtkRenderWindow::SafeDownCast(object))
+ {
+ renderWindow->Render();
+ return true;
+ }
+ return false;
+}
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::ResetCamera(vtkTypeUInt32 identifier)
+{
+ auto object = this->GetObjectAtId(identifier);
+ if (auto renderer = vtkRenderer::SafeDownCast(object))
+ {
+ renderer->ResetCamera();
+ return true;
+ }
+ return false;
+}
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::StartEventLoop(vtkTypeUInt32 identifier)
+{
+ vtkRenderWindowInteractor::InteractorManagesTheEventLoop = false;
+ auto object = this->GetObjectAtId(identifier);
+ if (auto* renderWindow = vtkRenderWindow::SafeDownCast(object))
+ {
+ if (auto* interactor =
+ vtkWebAssemblyRenderWindowInteractor::SafeDownCast(renderWindow->GetInteractor()))
+ {
+ if (auto* wasmGLWindow = vtkWebAssemblyOpenGLRenderWindow::SafeDownCast(renderWindow))
+ {
+ // copy canvas selector from the render window to the interactor.
+ interactor->SetCanvasSelector(wasmGLWindow->GetCanvasSelector());
+ std::cout << "Started event loop id=" << identifier
+ << ", interactor=" << interactor->GetObjectDescription() << '\n';
+ interactor->Start();
+ return true;
+ }
+ else
+ {
+ std::cerr << "Render window class " << renderWindow->GetClassName()
+ << " is not recognized!\n";
+ }
+ }
+ else
+ {
+ std::cerr << "Interactor class " << renderWindow->GetClassName() << " is not recognized!\n";
+ }
+ }
+ return false;
+}
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::StopEventLoop(vtkTypeUInt32 identifier)
+{
+ auto object = this->GetObjectAtId(identifier);
+ if (auto renderWindow = vtkRenderWindow::SafeDownCast(object))
+ {
+ auto interactor = renderWindow->GetInteractor();
+ std::cout << "Stopping event loop id=" << identifier
+ << ", interactor=" << interactor->GetObjectDescription() << '\n';
+ interactor->TerminateApp();
+ return true;
+ }
+ return false;
+}
+
+namespace
+{
+struct CallbackBridge
+{
+ vtkWasmSceneManager::ObserverCallbackF f;
+ vtkTypeUInt32 SenderId;
+};
+}
+
+//-------------------------------------------------------------------------------
+unsigned long vtkWasmSceneManager::AddObserver(
+ vtkTypeUInt32 identifier, std::string eventName, ObserverCallbackF callback)
+{
+ auto object = vtkObject::SafeDownCast(this->GetObjectAtId(identifier));
+ if (object == nullptr)
+ {
+ return 0;
+ }
+ vtkNew<vtkCallbackCommand> callbackCmd;
+ callbackCmd->SetClientData(new CallbackBridge{ callback, identifier });
+ callbackCmd->SetClientDataDeleteCallback(
+ [](void* clientData)
+ {
+ auto* bridge = reinterpret_cast<CallbackBridge*>(clientData);
+ delete bridge;
+ });
+ callbackCmd->SetCallback(
+ [](vtkObject*, unsigned long eid, void* clientData, void*)
+ {
+ auto* bridge = reinterpret_cast<CallbackBridge*>(clientData);
+ bridge->f(bridge->SenderId, vtkCommand::GetStringFromEventId(eid));
+ });
+ return object->AddObserver(eventName.c_str(), callbackCmd);
+}
+
+//-------------------------------------------------------------------------------
+bool vtkWasmSceneManager::RemoveObserver(vtkTypeUInt32 identifier, unsigned long tag)
+{
+
+ auto object = vtkObject::SafeDownCast(this->GetObjectAtId(identifier));
+ if (object == nullptr)
+ {
+ return false;
+ }
+ object->RemoveObserver(tag);
+ return true;
+}
+
+bool vtkWasmSceneManager::BindRenderWindow(
+ vtkTypeUInt32 renderWindowIdentifier, const char* canvasSelector)
+{
+ if (auto* renderWindow =
+ vtkRenderWindow::SafeDownCast(this->GetObjectAtId(renderWindowIdentifier)))
+ {
+ if (auto* wasmGLWindow = vtkWebAssemblyOpenGLRenderWindow::SafeDownCast(renderWindow))
+ {
+ wasmGLWindow->SetCanvasSelector(canvasSelector);
+ if (auto* interactor =
+ vtkWebAssemblyRenderWindowInteractor::SafeDownCast(renderWindow->GetInteractor()))
+ {
+ interactor->SetCanvasSelector(canvasSelector);
+ return true;
+ }
+ else
+ {
+ std::cerr << "No interactor found for render window with identifier: "
+ << renderWindowIdentifier << '\n';
+ return false;
+ }
+ }
+ else
+ {
+ std::cerr << "Render window class " << renderWindow->GetClassName()
+ << " is not recognized!\n";
+ return false;
+ }
+ }
+ else
+ {
+ std::cerr << "No render window found with identifier: " << renderWindowIdentifier << '\n';
+ return false;
+ }
+}
+
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWasmSceneManager
+ * @brief vtkWasmSceneManager provides additional functionality that relates to a vtkRenderWindow
+ * and user interaction.
+ *
+ * `vtkWasmSceneManager` is a javascript wrapper of `vtkSceneManager` for managing VTK
+ * objects, specifically designed for webassembly (wasm). It extends
+ * functionality of `vtkObjectManager` for managing objects such as `vtkRenderWindow`,
+ * `vtkRenderWindowInteractor` and enables event-observers in webassembly
+ * visualization applications.
+ *
+ * @sa vtkObjectManager
+ */
+#ifndef vtkWasmSceneManager_h
+#define vtkWasmSceneManager_h
+
+#include "vtkObjectManager.h"
+
+#include "vtkSerializationManagerModule.h" // for export macro
+
+VTK_ABI_NAMESPACE_BEGIN
+
+class VTKSERIALIZATIONMANAGER_EXPORT vtkWasmSceneManager : public vtkObjectManager
+{
+public:
+ static vtkWasmSceneManager* New();
+ vtkTypeMacro(vtkWasmSceneManager, vtkObjectManager);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ bool Initialize() override;
+
+ /**
+ * Set the size of the `vtkRenderWindow` object at `identifier` to
+ * the supplied dimensions.
+ *
+ * Returns `true` if the object at `identifier` is a `vtkRenderWindow`
+ * with a `vtkRenderWindowInteractor` attached to it,
+ * `false` otherwise.
+ */
+ bool SetSize(vtkTypeUInt32 identifier, int width, int height);
+
+ /**
+ * Render the `vtkRenderWindow` object at `identifier`.
+ *
+ * Returns `true` if the object at `identifier` is a `vtkRenderWindow`
+ * `false` otherwise.
+ */
+ bool Render(vtkTypeUInt32 identifier);
+
+ /**
+ * Reset the active camera of the `vtkRenderer` object at `identifier`.
+ *
+ * Returns `true` if the object at `identifier` is a `vtkRenderer`
+ * `false` otherwise.
+ */
+ bool ResetCamera(vtkTypeUInt32 identifier);
+
+ /**
+ * Start event loop of the `vtkRenderWindowInteractor` object at `identifier`.
+ *
+ * Returns `true` if the object at `identifier` is a `vtkRenderWindowInteractor`
+ * `false` otherwise.
+ */
+ bool StartEventLoop(vtkTypeUInt32 identifier);
+
+ /**
+ * Stop event loop of the `vtkRenderWindowInteractor` object at `identifier`.
+ *
+ * Returns `true` if the object at `identifier` is a `vtkRenderWindowInteractor`
+ * `false` otherwise.
+ */
+ bool StopEventLoop(vtkTypeUInt32 identifier);
+
+ typedef void (*ObserverCallbackF)(vtkTypeUInt32, const char*);
+
+ /**
+ * Observes `eventName` event emitted by an object registered at `identifier`
+ * and invokes `callback` with the `identifier` and `eventName` for every such emission.
+ *
+ * Returns the tag of an observer for `eventName`. You can use the tag in `RemoveObserver`
+ * to stop observing `eventName` event from the object at `identifier`
+ */
+ unsigned long AddObserver(
+ vtkTypeUInt32 identifier, std::string eventName, ObserverCallbackF callback);
+
+ /**
+ * Stop observing the object at `identifier`.
+ * Returns `true` if an object exists at `identifier`,
+ * `false` otherwise.
+ */
+ bool RemoveObserver(vtkTypeUInt32 identifier, unsigned long tag);
+
+ /**
+ * Bind a `vtkRenderWindow` object at `renderWindowIdentifier` to a canvas element with the
+ * specified `canvasSelector`. This allows the `vtkRenderWindow` to render its content onto the
+ * specified HTML canvas element in a web application.
+ *
+ * @param renderWindowIdentifier The identifier of the `vtkRenderWindow` object to bind.
+ * @param canvasSelector The ID of the HTML canvas element to bind the `vtkRenderWindow` to.
+ */
+ bool BindRenderWindow(vtkTypeUInt32 renderWindowIdentifier, const char* canvasSelector);
+
+protected:
+ vtkWasmSceneManager();
+ ~vtkWasmSceneManager() override;
+
+private:
+ vtkWasmSceneManager(const vtkWasmSceneManager&) = delete;
+ void operator=(const vtkWasmSceneManager&) = delete;
+};
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include <emscripten.h>
+#include <emscripten/bind.h>
+
+#include "vtkDataArrayRange.h"
+#include "vtkTypeUInt8Array.h"
+#include "vtkVersion.h"
+#include "vtkWasmSceneManager.h"
+
+// clang-format off
+#include "vtk_nlohmannjson.h" // for json
+#include VTK_NLOHMANN_JSON(json_fwd.hpp) // for json
+// clang-format on
+
+#include <map>
+#include <set>
+
+namespace
+{
+
+#define CHECK_INIT \
+ do \
+ { \
+ if (Manager == nullptr) \
+ { \
+ std::cerr << "Manager is null. Did you call forget to call initialize()?\n"; \
+ } \
+ } while (0)
+
+vtkWasmSceneManager* Manager = nullptr;
+
+std::map<std::string, std::set<std::string>> SkippedClassProperties;
+
+using namespace emscripten;
+
+thread_local const val Uint8Array = val::global("Uint8Array");
+thread_local const val Uint32Array = val::global("Uint32Array");
+thread_local const val JSON = val::global("JSON");
+
+//-------------------------------------------------------------------------------
+bool initialize()
+{
+ Manager = vtkWasmSceneManager::New();
+ return Manager->Initialize();
+}
+
+//-------------------------------------------------------------------------------
+void finalize()
+{
+ CHECK_INIT;
+ Manager->UnRegister(nullptr);
+}
+
+//-------------------------------------------------------------------------------
+bool registerState(const std::string& state)
+{
+ CHECK_INIT;
+ auto stateJson = nlohmann::json::parse(state, nullptr, false);
+ if (stateJson.is_discarded())
+ {
+ vtkErrorWithObjectMacro(Manager, << "Failed to parse state!");
+ return false;
+ }
+ if (auto classNameIter = stateJson.find("ClassName"); classNameIter != stateJson.end())
+ {
+ if (*classNameIter == "vtkOSOpenGLRenderWindow")
+ {
+ *classNameIter = "vtkWebAssemblyOpenGLRenderWindow";
+ }
+ if (auto propertiesIter = SkippedClassProperties.find(*classNameIter);
+ propertiesIter != SkippedClassProperties.end())
+ {
+ for (const auto& propertyName : propertiesIter->second)
+ {
+ stateJson.erase(propertyName);
+ }
+ }
+ }
+ return Manager->RegisterState(stateJson);
+}
+
+//-------------------------------------------------------------------------------
+bool registerState(val stateJavaScriptJSON)
+{
+ CHECK_INIT;
+ const auto stringified = JSON.call<val>("stringify", stateJavaScriptJSON);
+ return ::registerState(stringified.as<std::string>());
+}
+
+//-------------------------------------------------------------------------------
+bool unRegisterState(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return Manager->UnRegisterState(identifier);
+}
+
+//-------------------------------------------------------------------------------
+val getState(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return JSON.call<val>("parse", Manager->GetState(identifier));
+}
+
+//-------------------------------------------------------------------------------
+void skipProperty(const std::string& className, const std::string& propertyName)
+{
+ SkippedClassProperties[className].insert(propertyName);
+}
+
+//-------------------------------------------------------------------------------
+void unSkipProperty(const std::string& className, const std::string& propertyName)
+{
+ SkippedClassProperties[className].erase(propertyName);
+}
+
+//-------------------------------------------------------------------------------
+bool unRegisterObject(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return Manager->UnRegisterObject(identifier);
+}
+
+//-------------------------------------------------------------------------------
+bool registerBlob(const std::string& hash, val jsArray)
+{
+ CHECK_INIT;
+ if (jsArray.instanceof (val::global("Uint8Array")))
+ {
+ const vtkIdType l = jsArray["length"].as<vtkIdType>();
+ auto blob = vtk::TakeSmartPointer(vtkTypeUInt8Array::New());
+ blob->SetNumberOfValues(l);
+ auto blobRange = vtk::DataArrayValueRange(blob);
+ val memoryView{ typed_memory_view(static_cast<std::size_t>(l), blobRange.data()) };
+ memoryView.call<void>("set", jsArray);
+ return Manager->RegisterBlob(hash, blob);
+ }
+ else
+ {
+ std::cerr << "Invalid type! Expects instanceof blob == Uint8Array" << std::endl;
+ return false;
+ }
+}
+
+//-------------------------------------------------------------------------------
+bool unRegisterBlob(const std::string& hash)
+{
+ CHECK_INIT;
+ return Manager->UnRegisterBlob(hash);
+}
+
+//-------------------------------------------------------------------------------
+val getBlob(const std::string& hash)
+{
+ CHECK_INIT;
+ const auto blob = Manager->GetBlob(hash);
+ val jsBlob = Uint8Array.new_(typed_memory_view(blob->GetNumberOfValues(), blob->GetPointer(0)));
+ return jsBlob;
+}
+
+//-------------------------------------------------------------------------------
+void pruneUnusedBlobs()
+{
+ CHECK_INIT;
+ Manager->PruneUnusedBlobs();
+}
+
+//-------------------------------------------------------------------------------
+void clear()
+{
+ CHECK_INIT;
+ Manager->Clear();
+}
+
+//-------------------------------------------------------------------------------
+val invoke(vtkTypeUInt32 identifier, const std::string& methodName, val args)
+{
+ CHECK_INIT;
+ const auto stringified = JSON.call<val>("stringify", args);
+ return JSON.call<val>(
+ "parse", Manager->Invoke(identifier, methodName, stringified.as<std::string>()));
+}
+
+//-------------------------------------------------------------------------------
+val getAllDependencies(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ const auto ids = Manager->GetAllDependencies(identifier);
+ val jsIds = Uint32Array.new_(typed_memory_view(ids.size(), ids.data()));
+ return jsIds;
+}
+//-------------------------------------------------------------------------------
+std::size_t getTotalBlobMemoryUsage()
+{
+ CHECK_INIT;
+ return ::Manager->GetTotalBlobMemoryUsage();
+}
+
+//-------------------------------------------------------------------------------
+std::size_t getTotalVTKDataObjectMemoryUsage()
+{
+ CHECK_INIT;
+ return ::Manager->GetTotalVTKDataObjectMemoryUsage();
+}
+
+//-------------------------------------------------------------------------------
+void updateObjectsFromStates()
+{
+ CHECK_INIT;
+ Manager->UpdateObjectsFromStates();
+}
+
+//-------------------------------------------------------------------------------
+void updateStatesFromObjects()
+{
+ CHECK_INIT;
+ Manager->UpdateStatesFromObjects();
+}
+
+//-------------------------------------------------------------------------------
+void updateObjectFromState(const std::string& state)
+{
+ CHECK_INIT;
+ auto stateJson = nlohmann::json::parse(state, nullptr, false);
+ if (stateJson.is_discarded())
+ {
+ vtkErrorWithObjectMacro(Manager, << "Failed to parse state!");
+ }
+ else if (auto idIter = stateJson.find("Id"); idIter != stateJson.end())
+ {
+ if (auto classNameIter = stateJson.find("ClassName"); classNameIter != stateJson.end())
+ {
+ if (*classNameIter == "vtkOSOpenGLRenderWindow")
+ {
+ *classNameIter = "vtkWebAssemblyOpenGLRenderWindow";
+ }
+ }
+ if (auto objectAtId = Manager->GetObjectAtId(*idIter))
+ {
+ const std::string className = objectAtId->GetClassName();
+ if (auto propertiesIter = SkippedClassProperties.find(className);
+ propertiesIter != SkippedClassProperties.end())
+ {
+ for (const auto& propertyName : propertiesIter->second)
+ {
+ stateJson.erase(propertyName);
+ }
+ }
+ }
+ }
+ Manager->UpdateObjectFromState(stateJson);
+}
+
+//-------------------------------------------------------------------------------
+void updateObjectFromState(val stateJavaScriptJSON)
+{
+ CHECK_INIT;
+ const auto stringified = JSON.call<val>("stringify", stateJavaScriptJSON);
+ updateObjectFromState(stringified.as<std::string>());
+}
+
+//-------------------------------------------------------------------------------
+void updateStateFromObject(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ Manager->UpdateStateFromObject(identifier);
+}
+
+//-------------------------------------------------------------------------------
+bool setSize(vtkTypeUInt32 identifier, int width, int height)
+{
+ CHECK_INIT;
+ return Manager->SetSize(identifier, width, height);
+}
+
+//-------------------------------------------------------------------------------
+bool render(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return Manager->Render(identifier);
+}
+
+//-------------------------------------------------------------------------------
+bool resetCamera(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return Manager->ResetCamera(identifier);
+}
+
+//-------------------------------------------------------------------------------
+bool startEventLoop(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return Manager->StartEventLoop(identifier);
+}
+
+//-------------------------------------------------------------------------------
+bool stopEventLoop(vtkTypeUInt32 identifier)
+{
+ CHECK_INIT;
+ return Manager->StopEventLoop(identifier);
+}
+
+//-------------------------------------------------------------------------------
+unsigned long addObserver(vtkTypeUInt32 identifier, std::string eventName, val jsFunc)
+{
+ CHECK_INIT;
+ int fp = val::module_property("addFunction")(jsFunc, std::string("vii")).as<int>();
+ auto callback = reinterpret_cast<vtkWasmSceneManager::ObserverCallbackF>(fp);
+ return Manager->AddObserver(identifier, eventName, callback);
+}
+
+//-------------------------------------------------------------------------------
+bool removeObserver(vtkTypeUInt32 identifier, unsigned long tag)
+{
+ CHECK_INIT;
+ return Manager->RemoveObserver(identifier, tag);
+}
+
+//-------------------------------------------------------------------------------
+bool bindRenderWindow(vtkTypeUInt32 renderWindowIdentifier, const std::string& canvasSelector)
+{
+ CHECK_INIT;
+ return Manager->BindRenderWindow(renderWindowIdentifier, canvasSelector.c_str());
+}
+
+//-------------------------------------------------------------------------------
+void import(const std::string& stateFileName, const std::string& blobFileName)
+{
+ CHECK_INIT;
+ Manager->Import(stateFileName, blobFileName);
+}
+
+//-------------------------------------------------------------------------------
+void printSceneManagerInformation()
+{
+ CHECK_INIT;
+ Manager->Print(std::cout);
+}
+
+//-------------------------------------------------------------------------------
+void setDeserializerLogVerbosity(const std::string& verbosityStr)
+{
+ CHECK_INIT;
+ const auto verbosity = vtkLogger::ConvertToVerbosity(verbosityStr.c_str());
+ if (verbosity != vtkLogger::VERBOSITY_INVALID)
+ {
+ Manager->GetDeserializer()->SetDeserializerLogVerbosity(verbosity);
+ }
+}
+
+//-------------------------------------------------------------------------------
+void setInvokerLogVerbosity(const std::string& verbosityStr)
+{
+ CHECK_INIT;
+ const auto verbosity = vtkLogger::ConvertToVerbosity(verbosityStr.c_str());
+ if (verbosity != vtkLogger::VERBOSITY_INVALID)
+ {
+ Manager->GetInvoker()->SetInvokerLogVerbosity(verbosity);
+ }
+}
+
+//-------------------------------------------------------------------------------
+void setObjectManagerLogVerbosity(const std::string& verbosityStr)
+{
+ CHECK_INIT;
+ const auto verbosity = vtkLogger::ConvertToVerbosity(verbosityStr.c_str());
+ if (verbosity != vtkLogger::VERBOSITY_INVALID)
+ {
+ Manager->SetObjectManagerLogVerbosity(verbosity);
+ }
+}
+
+//-------------------------------------------------------------------------------
+void setSerializerLogVerbosity(const std::string& verbosityStr)
+{
+ CHECK_INIT;
+ const auto verbosity = vtkLogger::ConvertToVerbosity(verbosityStr.c_str());
+ if (verbosity != vtkLogger::VERBOSITY_INVALID)
+ {
+ Manager->GetSerializer()->SetSerializerLogVerbosity(verbosity);
+ }
+}
+
+//-------------------------------------------------------------------------------
+std::string getVTKVersion()
+{
+ return vtkVersion::GetVTKVersion();
+}
+
+//-------------------------------------------------------------------------------
+std::string getVTKVersionFull()
+{
+ return vtkVersion::GetVTKVersionFull();
+}
+
+} // namespace
+
+EMSCRIPTEN_BINDINGS(vtkWasmSceneManager)
+{
+ function("initialize", ::initialize);
+ function("finalize", ::finalize);
+
+ function("registerState", select_overload<bool(const std::string&)>(::registerState));
+ function("registerStateJSON", select_overload<bool(val)>(::registerState));
+ function("unRegisterState", ::unRegisterState);
+ function("getState", ::getState);
+ function("skipProperty", ::skipProperty);
+ function("unSkipProperty", ::unSkipProperty);
+
+ function("unRegisterObject", ::unRegisterObject);
+
+ function("registerBlob", ::registerBlob);
+ function("unRegisterBlob", ::unRegisterBlob);
+ function("getBlob", ::getBlob);
+ function("pruneUnusedBlobs", ::pruneUnusedBlobs);
+
+ function("clear", ::clear);
+ function("invoke", ::invoke);
+
+ function("getAllDependencies", ::getAllDependencies);
+
+ function("getTotalBlobMemoryUsage", ::getTotalBlobMemoryUsage);
+ function("getTotalVTKDataObjectMemoryUsage", ::getTotalVTKDataObjectMemoryUsage);
+
+ function("updateObjectsFromStates", ::updateObjectsFromStates);
+ function("updateStatesFromObjects", ::updateStatesFromObjects);
+
+ function(
+ "updateObjectFromState", select_overload<void(const std::string&)>(::updateObjectFromState));
+ function("updateObjectFromStateJSON", select_overload<void(val)>(::updateObjectFromState));
+ function("updateStateFromObject", ::updateStateFromObject);
+
+ function("setSize", ::setSize);
+ function("render", ::render);
+ function("resetCamera", ::resetCamera);
+
+ function("startEventLoop", ::startEventLoop);
+ function("stopEventLoop", ::stopEventLoop);
+
+ function("addObserver", ::addObserver);
+ function("removeObserver", ::removeObserver);
+
+ function("bindRenderWindow", ::bindRenderWindow);
+
+ function("import", ::import);
+
+ // debugging
+ function("printSceneManagerInformation", ::printSceneManagerInformation);
+ // accepts JS strings like "INFO", "WARNING", "TRACE", "ERROR"
+ function("setDeserializerLogVerbosity", ::setDeserializerLogVerbosity);
+ function("setInvokerLogVerbosity", ::setInvokerLogVerbosity);
+ function("setObjectManagerLogVerbosity", ::setObjectManagerLogVerbosity);
+ function("setSerializerLogVerbosity", ::setSerializerLogVerbosity);
+
+ function("getVTKVersion", ::getVTKVersion);
+ function("getVTKVersionFull", ::getVTKVersionFull);
+}
+
+int main()
+{
+ return 0;
+}
--- /dev/null
+# The exporter will behave as any other ParaView exporter (VRML, X3D, POV...)
+# but will generate several types of files. The main one is the scene graph
+# description define as a JSON object with all the corresponding binary+base64
+# pieces that come along with it. But also with it come a single standalone HTML
+# file that can directly be used to see the data in a browser without any plugin.
+#
+# This code base should be cleaned up to follow VTK standard and even be
+# integrated into VTK itself. But for now it is provided as is.
+
+set(classes
+ vtkPVWebGLExporter
+ vtkWebGLDataSet
+ vtkWebGLExporter
+ vtkWebGLObject
+ vtkWebGLPolyData
+ vtkWebGLWidget)
+
+set(javascript_files
+ webglRenderer.js
+ glMatrix.js)
+
+set(sources)
+set(private_headers)
+
+foreach (javascript_file IN LISTS javascript_files)
+ vtk_encode_string(
+ INPUT "${javascript_file}"
+ EXPORT_HEADER "vtkWebGLExporterModule.h"
+ EXPORT_SYMBOL "VTKWEBGLEXPORTER_NO_EXPORT"
+ HEADER_OUTPUT header
+ SOURCE_OUTPUT source)
+ list(APPEND sources
+ ${source})
+ list(APPEND private_headers
+ ${header})
+endforeach ()
+
+vtk_module_add_module(VTK::WebGLExporter
+ CLASSES ${classes}
+ SOURCES ${sources}
+ PRIVATE_HEADERS ${private_headers})
+vtk_add_test_mangling(VTK::WebGLExporter)
--- /dev/null
+// glMatrix v0.9.5
+glMatrixArrayType=typeof Float32Array!="undefined"?Float32Array:typeof WebGLFloatArray!="undefined"?WebGLFloatArray:Array;var vec3={};vec3.create=function(a){var b=new glMatrixArrayType(3);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2]}return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,c){if(!c||a==c){a[0]+=b[0];a[1]+=b[1];a[2]+=b[2];return a}c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];return c};
+vec3.subtract=function(a,b,c){if(!c||a==c){a[0]-=b[0];a[1]-=b[1];a[2]-=b[2];return a}c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];return c};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,c){if(!c||a==c){a[0]*=b;a[1]*=b;a[2]*=b;return a}c[0]=a[0]*b;c[1]=a[1]*b;c[2]=a[2]*b;return c};
+vec3.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=Math.sqrt(c*c+d*d+e*e);if(g){if(g==1){b[0]=c;b[1]=d;b[2]=e;return b}}else{b[0]=0;b[1]=0;b[2]=0;return b}g=1/g;b[0]=c*g;b[1]=d*g;b[2]=e*g;return b};vec3.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1];a=a[2];var g=b[0],f=b[1];b=b[2];c[0]=e*b-a*f;c[1]=a*g-d*b;c[2]=d*f-e*g;return c};vec3.length=function(a){var b=a[0],c=a[1];a=a[2];return Math.sqrt(b*b+c*c+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]};
+vec3.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1];a=a[2]-b[2];b=Math.sqrt(d*d+e*e+a*a);if(!b){c[0]=0;c[1]=0;c[2]=0;return c}b=1/b;c[0]=d*b;c[1]=e*b;c[2]=a*b;return c};vec3.lerp=function(a,b,c,d){d||(d=a);d[0]=a[0]+c*(b[0]-a[0]);d[1]=a[1]+c*(b[1]-a[1]);d[2]=a[2]+c*(b[2]-a[2]);return d};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};var mat3={};
+mat3.create=function(a){var b=new glMatrixArrayType(9);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9]}return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a};
+mat3.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[5];a[1]=a[3];a[2]=a[6];a[3]=c;a[5]=a[7];a[6]=d;a[7]=e;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=0;b[4]=a[3];b[5]=a[4];b[6]=a[5];b[7]=0;b[8]=a[6];b[9]=a[7];b[10]=a[8];b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};
+mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};var mat4={};mat4.create=function(a){var b=new glMatrixArrayType(16);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15]}return b};
+mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a};
+mat4.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[3],g=a[6],f=a[7],h=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=c;a[6]=a[9];a[7]=a[13];a[8]=d;a[9]=g;a[11]=a[14];a[12]=e;a[13]=f;a[14]=h;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b};
+mat4.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],g=a[4],f=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],o=a[11],m=a[12],n=a[13],p=a[14];a=a[15];return m*k*h*e-j*n*h*e-m*f*l*e+g*n*l*e+j*f*p*e-g*k*p*e-m*k*d*i+j*n*d*i+m*c*l*i-b*n*l*i-j*c*p*i+b*k*p*i+m*f*d*o-g*n*d*o-m*c*h*o+b*n*h*o+g*c*p*o-b*f*p*o-j*f*d*a+g*k*d*a+j*c*h*a-b*k*h*a-g*c*l*a+b*f*l*a};
+mat4.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],o=a[10],m=a[11],n=a[12],p=a[13],r=a[14],s=a[15],A=c*h-d*f,B=c*i-e*f,t=c*j-g*f,u=d*i-e*h,v=d*j-g*h,w=e*j-g*i,x=k*p-l*n,y=k*r-o*n,z=k*s-m*n,C=l*r-o*p,D=l*s-m*p,E=o*s-m*r,q=1/(A*E-B*D+t*C+u*z-v*y+w*x);b[0]=(h*E-i*D+j*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+o*v-m*u)*q;b[4]=(-f*E+i*z-j*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-n*w+r*t-s*B)*q;b[7]=(k*w-o*t+m*B)*q;b[8]=(f*D-h*z+j*x)*q;
+b[9]=(-c*D+d*z-g*x)*q;b[10]=(n*v-p*t+s*A)*q;b[11]=(-k*v+l*t-m*A)*q;b[12]=(-f*C+h*y-i*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-n*u+p*B-r*A)*q;b[15]=(k*u-l*B+o*A)*q;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};
+mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],f=a[5],h=a[6],i=a[8],j=a[9],k=a[10],l=k*f-h*j,o=-k*g+h*i,m=j*g-f*i,n=c*l+d*o+e*m;if(!n)return null;n=1/n;b||(b=mat3.create());b[0]=l*n;b[1]=(-k*d+e*j)*n;b[2]=(h*d-e*f)*n;b[3]=o*n;b[4]=(k*c-e*i)*n;b[5]=(-h*c+e*g)*n;b[6]=m*n;b[7]=(-j*c+d*i)*n;b[8]=(f*c-d*g)*n;return b};
+mat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],f=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],o=a[9],m=a[10],n=a[11],p=a[12],r=a[13],s=a[14];a=a[15];var A=b[0],B=b[1],t=b[2],u=b[3],v=b[4],w=b[5],x=b[6],y=b[7],z=b[8],C=b[9],D=b[10],E=b[11],q=b[12],F=b[13],G=b[14];b=b[15];c[0]=A*d+B*h+t*l+u*p;c[1]=A*e+B*i+t*o+u*r;c[2]=A*g+B*j+t*m+u*s;c[3]=A*f+B*k+t*n+u*a;c[4]=v*d+w*h+x*l+y*p;c[5]=v*e+w*i+x*o+y*r;c[6]=v*g+w*j+x*m+y*s;c[7]=v*f+w*k+x*n+y*a;c[8]=z*d+C*h+D*l+E*p;c[9]=z*e+C*i+D*o+E*r;c[10]=z*
+g+C*j+D*m+E*s;c[11]=z*f+C*k+D*n+E*a;c[12]=q*d+F*h+G*l+b*p;c[13]=q*e+F*i+G*o+b*r;c[14]=q*g+F*j+G*m+b*s;c[15]=q*f+F*k+G*n+b*a;return c};mat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1];b=b[2];c[0]=a[0]*d+a[4]*e+a[8]*b+a[12];c[1]=a[1]*d+a[5]*e+a[9]*b+a[13];c[2]=a[2]*d+a[6]*e+a[10]*b+a[14];return c};
+mat4.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2];b=b[3];c[0]=a[0]*d+a[4]*e+a[8]*g+a[12]*b;c[1]=a[1]*d+a[5]*e+a[9]*g+a[13]*b;c[2]=a[2]*d+a[6]*e+a[10]*g+a[14]*b;c[3]=a[3]*d+a[7]*e+a[11]*g+a[15]*b;return c};
+mat4.translate=function(a,b,c){var d=b[0],e=b[1];b=b[2];if(!c||a==c){a[12]=a[0]*d+a[4]*e+a[8]*b+a[12];a[13]=a[1]*d+a[5]*e+a[9]*b+a[13];a[14]=a[2]*d+a[6]*e+a[10]*b+a[14];a[15]=a[3]*d+a[7]*e+a[11]*b+a[15];return a}var g=a[0],f=a[1],h=a[2],i=a[3],j=a[4],k=a[5],l=a[6],o=a[7],m=a[8],n=a[9],p=a[10],r=a[11];c[0]=g;c[1]=f;c[2]=h;c[3]=i;c[4]=j;c[5]=k;c[6]=l;c[7]=o;c[8]=m;c[9]=n;c[10]=p;c[11]=r;c[12]=g*d+j*e+m*b+a[12];c[13]=f*d+k*e+n*b+a[13];c[14]=h*d+l*e+p*b+a[14];c[15]=i*d+o*e+r*b+a[15];return c};
+mat4.scale=function(a,b,c){var d=b[0],e=b[1];b=b[2];if(!c||a==c){a[0]*=d;a[1]*=d;a[2]*=d;a[3]*=d;a[4]*=e;a[5]*=e;a[6]*=e;a[7]*=e;a[8]*=b;a[9]*=b;a[10]*=b;a[11]*=b;return a}c[0]=a[0]*d;c[1]=a[1]*d;c[2]=a[2]*d;c[3]=a[3]*d;c[4]=a[4]*e;c[5]=a[5]*e;c[6]=a[6]*e;c[7]=a[7]*e;c[8]=a[8]*b;c[9]=a[9]*b;c[10]=a[10]*b;c[11]=a[11]*b;c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15];return c};
+mat4.rotate=function(a,b,c,d){var e=c[0],g=c[1];c=c[2];var f=Math.sqrt(e*e+g*g+c*c);if(!f)return null;if(f!=1){f=1/f;e*=f;g*=f;c*=f}var h=Math.sin(b),i=Math.cos(b),j=1-i;b=a[0];f=a[1];var k=a[2],l=a[3],o=a[4],m=a[5],n=a[6],p=a[7],r=a[8],s=a[9],A=a[10],B=a[11],t=e*e*j+i,u=g*e*j+c*h,v=c*e*j-g*h,w=e*g*j-c*h,x=g*g*j+i,y=c*g*j+e*h,z=e*c*j+g*h;e=g*c*j-e*h;g=c*c*j+i;if(d){if(a!=d){d[12]=a[12];d[13]=a[13];d[14]=a[14];d[15]=a[15]}}else d=a;d[0]=b*t+o*u+r*v;d[1]=f*t+m*u+s*v;d[2]=k*t+n*u+A*v;d[3]=l*t+p*u+B*
+v;d[4]=b*w+o*x+r*y;d[5]=f*w+m*x+s*y;d[6]=k*w+n*x+A*y;d[7]=l*w+p*x+B*y;d[8]=b*z+o*e+r*g;d[9]=f*z+m*e+s*g;d[10]=k*z+n*e+A*g;d[11]=l*z+p*e+B*g;return d};mat4.rotateX=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[4],g=a[5],f=a[6],h=a[7],i=a[8],j=a[9],k=a[10],l=a[11];if(c){if(a!=c){c[0]=a[0];c[1]=a[1];c[2]=a[2];c[3]=a[3];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[4]=e*b+i*d;c[5]=g*b+j*d;c[6]=f*b+k*d;c[7]=h*b+l*d;c[8]=e*-d+i*b;c[9]=g*-d+j*b;c[10]=f*-d+k*b;c[11]=h*-d+l*b;return c};
+mat4.rotateY=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[0],g=a[1],f=a[2],h=a[3],i=a[8],j=a[9],k=a[10],l=a[11];if(c){if(a!=c){c[4]=a[4];c[5]=a[5];c[6]=a[6];c[7]=a[7];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[0]=e*b+i*-d;c[1]=g*b+j*-d;c[2]=f*b+k*-d;c[3]=h*b+l*-d;c[8]=e*d+i*b;c[9]=g*d+j*b;c[10]=f*d+k*b;c[11]=h*d+l*b;return c};
+mat4.rotateZ=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[0],g=a[1],f=a[2],h=a[3],i=a[4],j=a[5],k=a[6],l=a[7];if(c){if(a!=c){c[8]=a[8];c[9]=a[9];c[10]=a[10];c[11]=a[11];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[0]=e*b+i*d;c[1]=g*b+j*d;c[2]=f*b+k*d;c[3]=h*b+l*d;c[4]=e*-d+i*b;c[5]=g*-d+j*b;c[6]=f*-d+k*b;c[7]=h*-d+l*b;return c};
+mat4.frustum=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=e*2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=e*2/i;f[6]=0;f[7]=0;f[8]=(b+a)/h;f[9]=(d+c)/i;f[10]=-(g+e)/j;f[11]=-1;f[12]=0;f[13]=0;f[14]=-(g*e*2)/j;f[15]=0;return f};mat4.perspective=function(a,b,c,d,e){a=c*Math.tan(a*Math.PI/360);b=a*b;return mat4.frustum(-b,b,-a,a,c,d,e)};
+mat4.ortho=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2/i;f[6]=0;f[7]=0;f[8]=0;f[9]=0;f[10]=-2/j;f[11]=0;f[12]=-(a+b)/h;f[13]=-(d+c)/i;f[14]=-(g+e)/j;f[15]=1;return f};
+mat4.lookAt=function(a,b,c,d){d||(d=mat4.create());var e=a[0],g=a[1];a=a[2];var f=c[0],h=c[1],i=c[2];c=b[1];var j=b[2];if(e==b[0]&&g==c&&a==j)return mat4.identity(d);var k,l,o,m;c=e-b[0];j=g-b[1];b=a-b[2];m=1/Math.sqrt(c*c+j*j+b*b);c*=m;j*=m;b*=m;k=h*b-i*j;i=i*c-f*b;f=f*j-h*c;if(m=Math.sqrt(k*k+i*i+f*f)){m=1/m;k*=m;i*=m;f*=m}else f=i=k=0;h=j*f-b*i;l=b*k-c*f;o=c*i-j*k;if(m=Math.sqrt(h*h+l*l+o*o)){m=1/m;h*=m;l*=m;o*=m}else o=l=h=0;d[0]=k;d[1]=h;d[2]=c;d[3]=0;d[4]=i;d[5]=l;d[6]=j;d[7]=0;d[8]=f;d[9]=
+o;d[10]=b;d[11]=0;d[12]=-(k*e+i*g+f*a);d[13]=-(h*e+l*g+o*a);d[14]=-(c*e+j*g+b*a);d[15]=1;return d};mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4={};quat4.create=function(a){var b=new glMatrixArrayType(4);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3]}return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b};
+quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];if(!b||a==b){a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return a}b[0]=c;b[1]=d;b[2]=e;b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return b};quat4.inverse=function(a,b){if(!b||a==b){a[0]*=1;a[1]*=1;a[2]*=1;return a}b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],c=a[1],d=a[2];a=a[3];return Math.sqrt(b*b+c*c+d*d+a*a)};
+quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=Math.sqrt(c*c+d*d+e*e+g*g);if(f==0){b[0]=0;b[1]=0;b[2]=0;b[3]=0;return b}f=1/f;b[0]=c*f;b[1]=d*f;b[2]=e*f;b[3]=g*f;return b};quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2];a=a[3];var f=b[0],h=b[1],i=b[2];b=b[3];c[0]=d*b+a*f+e*i-g*h;c[1]=e*b+a*h+g*f-d*i;c[2]=g*b+a*i+d*h-e*f;c[3]=a*b-d*f-e*h-g*i;return c};
+quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2];b=a[0];var f=a[1],h=a[2];a=a[3];var i=a*d+f*g-h*e,j=a*e+h*d-b*g,k=a*g+b*e-f*d;d=-b*d-f*e-h*g;c[0]=i*a+d*-b+j*-h-k*-f;c[1]=j*a+d*-f+k*-b-i*-h;c[2]=k*a+d*-h+i*-f-j*-b;return c};quat4.toMat3=function(a,b){b||(b=mat3.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c=c*i;var l=d*h;d=d*i;e=e*i;f=g*f;h=g*h;g=g*i;b[0]=1-(l+e);b[1]=k-g;b[2]=c+h;b[3]=k+g;b[4]=1-(j+e);b[5]=d-f;b[6]=c-h;b[7]=d+f;b[8]=1-(j+l);return b};
+quat4.toMat4=function(a,b){b||(b=mat4.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c=c*i;var l=d*h;d=d*i;e=e*i;f=g*f;h=g*h;g=g*i;b[0]=1-(l+e);b[1]=k-g;b[2]=c+h;b[3]=0;b[4]=k+g;b[5]=1-(j+e);b[6]=d-f;b[7]=0;b[8]=c-h;b[9]=d+f;b[10]=1-(j+l);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};quat4.slerp=function(a,b,c,d){d||(d=a);var e=c;if(a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]<0)e=-1*c;d[0]=1-c*a[0]+e*b[0];d[1]=1-c*a[1]+e*b[1];d[2]=1-c*a[2]+e*b[2];d[3]=1-c*a[3]+e*b[3];return d};
+quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"};
--- /dev/null
+NAME
+ VTK::WebGLExporter
+LIBRARY_NAME
+ vtkWebGLExporter
+GROUPS
+ Web
+SPDX_LICENSE_IDENTIFIER
+ BSD-3-Clause
+SPDX_COPYRIGHT_TEXT
+ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+DEPENDS
+ VTK::CommonCore
+ VTK::IOExport
+PRIVATE_DEPENDS
+ VTK::CommonDataModel
+ VTK::CommonMath
+ VTK::FiltersCore
+ VTK::FiltersGeometry
+ VTK::IOCore
+ VTK::InteractionWidgets
+ VTK::RenderingAnnotation
+ VTK::RenderingCore
+ VTK::vtksys
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#include "vtkPVWebGLExporter.h"
+
+#include "vtkBase64Utilities.h"
+#include "vtkCamera.h"
+#include "vtkExporter.h"
+#include "vtkNew.h"
+#include "vtkObjectFactory.h"
+#include "vtkRenderWindow.h"
+#include "vtkRenderer.h"
+#include "vtkRendererCollection.h"
+#include "vtkWebGLExporter.h"
+#include "vtkWebGLObject.h"
+
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vtksys/FStream.hxx>
+#include <vtksys/SystemTools.hxx>
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkPVWebGLExporter);
+//------------------------------------------------------------------------------
+vtkPVWebGLExporter::vtkPVWebGLExporter()
+{
+ this->FileName = nullptr;
+}
+
+//------------------------------------------------------------------------------
+vtkPVWebGLExporter::~vtkPVWebGLExporter()
+{
+ this->SetFileName(nullptr);
+}
+
+//------------------------------------------------------------------------------
+void vtkPVWebGLExporter::WriteData()
+{
+ // make sure the user specified a FileName or FilePointer
+ if (this->FileName == nullptr)
+ {
+ vtkErrorMacro(<< "Please specify FileName to use");
+ return;
+ }
+
+ vtkNew<vtkWebGLExporter> exporter;
+ exporter->SetMaxAllowedSize(65000);
+
+ // We use the camera focal point to be the center of rotation
+ double centerOfRotation[3];
+ vtkRenderer* ren = this->RenderWindow->GetRenderers()->GetFirstRenderer();
+ vtkCamera* cam = ren->GetActiveCamera();
+ cam->GetFocalPoint(centerOfRotation);
+ exporter->SetCenterOfRotation(static_cast<float>(centerOfRotation[0]),
+ static_cast<float>(centerOfRotation[1]), static_cast<float>(centerOfRotation[2]));
+
+ exporter->parseScene(this->RenderWindow->GetRenderers(), "1", VTK_PARSEALL);
+
+ // Write meta-data file
+ std::string baseFileName = this->FileName;
+ baseFileName.erase(baseFileName.size() - 6, 6);
+ std::string metadatFile = this->FileName;
+ FILE* fp = vtksys::SystemTools::Fopen(metadatFile, "w");
+ if (!fp)
+ {
+ vtkErrorMacro(<< "unable to open JSON MetaData file " << metadatFile);
+ return;
+ }
+ fputs(exporter->GenerateMetadata(), fp);
+ fclose(fp);
+
+ // Write binary objects
+ vtkNew<vtkBase64Utilities> base64;
+ int nbObjects = exporter->GetNumberOfObjects();
+ for (int idx = 0; idx < nbObjects; ++idx)
+ {
+ vtkWebGLObject* obj = exporter->GetWebGLObject(idx);
+ if (obj->isVisible())
+ {
+ int nbParts = obj->GetNumberOfParts();
+ for (int part = 0; part < nbParts; ++part)
+ {
+ // Manage binary content
+ std::stringstream filePath;
+ filePath << baseFileName << "_" << obj->GetMD5() << "_" << part;
+ vtksys::ofstream binaryFile;
+ binaryFile.open(filePath.str().c_str(), std::ios_base::out | std::ios_base::binary);
+ binaryFile.write((const char*)obj->GetBinaryData(part), obj->GetBinarySize(part));
+ binaryFile.close();
+
+ // Manage Base64
+ std::stringstream filePathBase64;
+ filePathBase64 << baseFileName << "_" << obj->GetMD5() << "_" << part << ".base64";
+ vtksys::ofstream base64File;
+ unsigned char* output = new unsigned char[obj->GetBinarySize(part) * 2];
+ int size =
+ base64->Encode(obj->GetBinaryData(part), obj->GetBinarySize(part), output, false);
+ base64File.open(filePathBase64.str().c_str(), std::ios_base::out);
+ base64File.write((const char*)output, size);
+ base64File.close();
+ delete[] output;
+ }
+ }
+ }
+
+ // Write HTML file
+ std::string htmlFile = baseFileName;
+ htmlFile += ".html";
+ exporter->exportStaticScene(this->RenderWindow->GetRenderers(), 300, 300, htmlFile);
+}
+//------------------------------------------------------------------------------
+void vtkPVWebGLExporter::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+
+ if (this->FileName)
+ {
+ os << indent << "FileName: " << this->FileName << "\n";
+ }
+ else
+ {
+ os << indent << "FileName: (null)\n";
+ }
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+#ifndef vtkPVWebGLExporter_h
+#define vtkPVWebGLExporter_h
+
+#include "vtkExporter.h"
+#include "vtkWebGLExporterModule.h" // needed for export macro
+
+VTK_ABI_NAMESPACE_BEGIN
+class VTKWEBGLEXPORTER_EXPORT vtkPVWebGLExporter : public vtkExporter
+{
+public:
+ static vtkPVWebGLExporter* New();
+ vtkTypeMacro(vtkPVWebGLExporter, vtkExporter);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ // Description:
+ // Specify the name of the VRML file to write.
+ vtkSetFilePathMacro(FileName);
+ vtkGetFilePathMacro(FileName);
+
+protected:
+ vtkPVWebGLExporter();
+ ~vtkPVWebGLExporter() override;
+
+ void WriteData() override;
+
+ char* FileName;
+
+private:
+ vtkPVWebGLExporter(const vtkPVWebGLExporter&) = delete;
+ void operator=(const vtkPVWebGLExporter&) = delete;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "vtkWebGLDataSet.h"
+
+#include "vtkObjectFactory.h"
+#include "vtkWebGLExporter.h"
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkWebGLDataSet);
+
+std::string vtkWebGLDataSet::GetMD5()
+{
+ return this->MD5;
+}
+
+vtkWebGLDataSet::vtkWebGLDataSet()
+{
+ this->NumberOfVertices = 0;
+ this->NumberOfPoints = 0;
+ this->NumberOfIndexes = 0;
+ this->vertices = nullptr;
+ this->normals = nullptr;
+ this->indexes = nullptr;
+ this->points = nullptr;
+ this->tcoords = nullptr;
+ this->colors = nullptr;
+ this->binary = nullptr;
+ this->binarySize = 0;
+ this->hasChanged = false;
+}
+
+vtkWebGLDataSet::~vtkWebGLDataSet()
+{
+ delete[] this->vertices;
+ delete[] this->normals;
+ delete[] this->indexes;
+ delete[] this->points;
+ delete[] this->tcoords;
+ delete[] this->colors;
+ delete[] this->binary;
+}
+
+void vtkWebGLDataSet::SetVertices(float* v, int size)
+{
+ delete[] this->vertices;
+ this->vertices = v;
+ this->NumberOfVertices = size;
+ this->webGLType = wTRIANGLES;
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::SetIndexes(short* i, int size)
+{
+ delete[] this->indexes;
+ this->indexes = i;
+ this->NumberOfIndexes = size;
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::SetNormals(float* n)
+{
+ delete[] this->normals;
+ this->normals = n;
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::SetColors(unsigned char* c)
+{
+ delete[] this->colors;
+ this->colors = c;
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::SetPoints(float* p, int size)
+{
+ delete[] this->points;
+ this->points = p;
+ this->NumberOfPoints = size;
+ this->webGLType = wLINES;
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::SetTCoords(float* t)
+{
+ delete[] this->tcoords;
+ this->tcoords = t;
+ this->hasChanged = true;
+}
+
+unsigned char* vtkWebGLDataSet::GetBinaryData()
+{
+ this->hasChanged = false;
+ return this->binary;
+}
+
+int vtkWebGLDataSet::GetBinarySize()
+{
+ return this->binarySize;
+}
+
+void vtkWebGLDataSet::SetMatrix(float* m)
+{
+ this->Matrix = m;
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::GenerateBinaryData()
+{
+ if (this->NumberOfIndexes == 0 && this->webGLType != wPOINTS)
+ {
+ return;
+ }
+ int size = 0, pos = 0, total = 0;
+ delete[] this->binary;
+ this->binarySize = 0;
+
+ if (this->webGLType == wLINES)
+ {
+ pos = sizeof(pos);
+ size = this->NumberOfPoints * sizeof(this->points[0]);
+
+ // Calculate the size used by each data
+ total = sizeof(pos) + 1 + sizeof(this->NumberOfPoints) +
+ size * 3 // Size, Type, NumberOfPoints, Points
+ + sizeof(this->colors[0]) * this->NumberOfPoints * 4 +
+ sizeof(this->NumberOfIndexes) // Color, NumberOfIndex
+ + this->NumberOfIndexes * sizeof(this->indexes[0]) +
+ sizeof(this->Matrix[0]) * 16; // Index, Matrix
+ this->binary = new unsigned char[total];
+ memset(this->binary, 0, total);
+
+ this->binary[pos++] = 'L';
+ memcpy(&this->binary[pos], &this->NumberOfPoints, sizeof(this->NumberOfPoints));
+ pos += sizeof(this->NumberOfPoints); // Points
+ memcpy(&this->binary[pos], this->points, size * 3);
+ pos += size * 3;
+ memcpy(&this->binary[pos], this->colors, sizeof(this->colors[0]) * this->NumberOfPoints * 4);
+ pos += sizeof(this->colors[0]) * this->NumberOfPoints * 4;
+ memcpy(&this->binary[pos], &this->NumberOfIndexes, sizeof(this->NumberOfIndexes));
+ pos += sizeof(this->NumberOfIndexes);
+ memcpy(&this->binary[pos], this->indexes, this->NumberOfIndexes * sizeof(this->indexes[0]));
+ pos += this->NumberOfIndexes * sizeof(this->indexes[0]);
+ memcpy(&this->binary[pos], this->Matrix, sizeof(this->Matrix[0]) * 16);
+ pos += sizeof(this->Matrix[0]) * 16; // Matrix
+
+ memcpy(&this->binary[0], &pos, sizeof(pos));
+ this->binarySize = total;
+ }
+ else if (this->webGLType == wTRIANGLES)
+ {
+ pos = sizeof(pos);
+ size = sizeof(this->vertices[0]) * this->NumberOfVertices;
+
+ // Calculate the size used by each data
+ total = sizeof(pos) + 1 + sizeof(this->NumberOfVertices) +
+ size * (3 + 3) // Size, Type, VertCount, Vert, Normal
+ + sizeof(this->colors[0]) * this->NumberOfVertices * 4 +
+ sizeof(this->NumberOfIndexes) // Color, IndicCount
+ + this->NumberOfIndexes * sizeof(this->indexes[0]) +
+ sizeof(this->Matrix[0]) * 16; // Index, Matrix
+ if (this->tcoords)
+ total += size * 2; // TCoord
+ this->binary = new unsigned char[total];
+ memset(this->binary, 0, total);
+
+ this->binary[pos++] = 'M';
+ memcpy(&this->binary[pos], &this->NumberOfVertices, sizeof(this->NumberOfVertices));
+ pos += sizeof(this->NumberOfVertices); // VertCount
+ memcpy(&this->binary[pos], this->vertices, size * 3);
+ pos += size * 3; // Vertices
+ memcpy(&this->binary[pos], this->normals, size * 3);
+ pos += size * 3; // Normals
+ memcpy(&this->binary[pos], this->colors, sizeof(this->colors[0]) * this->NumberOfVertices * 4);
+ pos += sizeof(this->colors[0]) * this->NumberOfVertices * 4; // Colors
+ memcpy(&this->binary[pos], &this->NumberOfIndexes, sizeof(this->NumberOfIndexes));
+ pos += sizeof(this->NumberOfIndexes); // IndCount
+ memcpy(&this->binary[pos], this->indexes, this->NumberOfIndexes * sizeof(this->indexes[0]));
+ pos += this->NumberOfIndexes * sizeof(this->indexes[0]);
+ memcpy(&this->binary[pos], this->Matrix, sizeof(this->Matrix[0]) * 16);
+ pos += sizeof(this->Matrix[0]) * 16; // Matrix
+ if (this->tcoords) // TCoord
+ {
+ memcpy(&this->binary[pos], this->tcoords, size * 2);
+ pos += size * 2;
+ }
+
+ memcpy(&this->binary[0], &pos, sizeof(pos));
+ this->binarySize = total;
+ }
+ else if (this->webGLType == wPOINTS)
+ {
+ pos = sizeof(pos);
+ size = this->NumberOfPoints * sizeof(this->points[0]);
+
+ // Calculate the size used by each data
+ total = sizeof(pos) + 1 + sizeof(this->NumberOfPoints) +
+ size * 3 // Size, Type, NumberOfPoints, Points
+ + sizeof(this->colors[0]) * this->NumberOfPoints * 4 +
+ sizeof(this->Matrix[0]) * 16; // Color, Matrix
+ this->binary = new unsigned char[total];
+ memset(this->binary, 0, total);
+
+ this->binary[pos++] = 'P';
+ memcpy(&this->binary[pos], &this->NumberOfPoints, sizeof(this->NumberOfPoints));
+ pos += sizeof(this->NumberOfPoints); // Points
+ memcpy(&this->binary[pos], this->points, size * 3);
+ pos += size * 3;
+ memcpy(&this->binary[pos], this->colors, sizeof(this->colors[0]) * this->NumberOfPoints * 4);
+ pos += sizeof(this->colors[0]) * this->NumberOfPoints * 4;
+ memcpy(&this->binary[pos], this->Matrix, sizeof(this->Matrix[0]) * 16);
+ pos += sizeof(this->Matrix[0]) * 16; // Matrix
+
+ memcpy(&this->binary[0], &pos, sizeof(pos));
+ this->binarySize = total;
+ }
+ vtkWebGLExporter::ComputeMD5((const unsigned char*)this->binary, this->binarySize, this->MD5);
+ this->hasChanged = true;
+}
+
+void vtkWebGLDataSet::SetType(WebGLObjectTypes t)
+{
+ this->webGLType = t;
+}
+
+bool vtkWebGLDataSet::HasChanged()
+{
+ return this->hasChanged;
+}
+
+void vtkWebGLDataSet::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebGLDataSet
+ * @brief vtkWebGLDataSet represent vertices, lines, polygons, and triangles.
+ */
+
+#ifndef vtkWebGLDataSet_h
+#define vtkWebGLDataSet_h
+
+#include "vtkObject.h"
+#include "vtkWebGLExporterModule.h" // needed for export macro
+
+#include "vtkWebGLObject.h" // Needed for the enum
+#include <string> // needed for md5
+
+VTK_ABI_NAMESPACE_BEGIN
+class VTKWEBGLEXPORTER_EXPORT vtkWebGLDataSet : public vtkObject
+{
+public:
+ static vtkWebGLDataSet* New();
+ vtkTypeMacro(vtkWebGLDataSet, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ void SetVertices(float* v, int size);
+ void SetIndexes(short* i, int size);
+ void SetNormals(float* n);
+ void SetColors(unsigned char* c);
+ void SetPoints(float* p, int size);
+ void SetTCoords(float* t);
+ void SetMatrix(float* m);
+ void SetType(WebGLObjectTypes t);
+
+ unsigned char* GetBinaryData();
+ int GetBinarySize();
+ void GenerateBinaryData();
+ bool HasChanged();
+
+ std::string GetMD5();
+
+protected:
+ vtkWebGLDataSet();
+ ~vtkWebGLDataSet() override;
+
+ int NumberOfVertices;
+ int NumberOfPoints;
+ int NumberOfIndexes;
+ WebGLObjectTypes webGLType;
+
+ float* Matrix;
+ float* vertices;
+ float* normals;
+ short* indexes;
+ float* points;
+ float* tcoords;
+ unsigned char* colors;
+ unsigned char* binary; // Data in binary
+ int binarySize; // Size of the data in binary
+ bool hasChanged;
+ std::string MD5;
+
+private:
+ vtkWebGLDataSet(const vtkWebGLDataSet&) = delete;
+ void operator=(const vtkWebGLDataSet&) = delete;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "vtkWebGLExporter.h"
+
+#include "vtkAbstractMapper.h"
+#include "vtkActor2D.h"
+#include "vtkActorCollection.h"
+#include "vtkBase64Utilities.h"
+#include "vtkCamera.h"
+#include "vtkCellArray.h"
+#include "vtkCellData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkDataSet.h"
+#include "vtkDataSetAttributes.h"
+#include "vtkDiscretizableColorTransferFunction.h"
+#include "vtkFollower.h"
+#include "vtkGenericCell.h"
+#include "vtkMapper.h"
+#include "vtkMapper2D.h"
+#include "vtkMatrix4x4.h"
+#include "vtkObjectFactory.h"
+#include "vtkPointData.h"
+#include "vtkPolyDataMapper2D.h"
+#include "vtkPolyDataNormals.h"
+#include "vtkProperty.h"
+#include "vtkProperty2D.h"
+#include "vtkRenderWindow.h"
+#include "vtkRenderer.h"
+#include "vtkRendererCollection.h"
+#include "vtkScalarBarActor.h"
+#include "vtkScalarBarRepresentation.h"
+#include "vtkSmartPointer.h"
+#include "vtkTriangleFilter.h"
+#include "vtkViewport.h"
+#include "vtkWidgetRepresentation.h"
+
+#include "vtkWebGLObject.h"
+#include "vtkWebGLPolyData.h"
+#include "vtkWebGLWidget.h"
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "glMatrix.h"
+#include "webglRenderer.h"
+
+#include "vtksys/FStream.hxx"
+#include "vtksys/MD5.h"
+#include "vtksys/SystemTools.hxx"
+
+//*****************************************************************************
+class vtkWebGLExporter::vtkInternal
+{
+public:
+ std::string LastMetaData;
+ std::map<vtkProp*, vtkMTimeType> ActorTimestamp;
+ std::map<vtkProp*, vtkMTimeType> OldActorTimestamp;
+ std::vector<vtkWebGLObject*> Objects;
+ std::vector<vtkWebGLObject*> tempObj;
+};
+//*****************************************************************************
+
+vtkStandardNewMacro(vtkWebGLExporter);
+
+vtkWebGLExporter::vtkWebGLExporter()
+{
+ this->meshObjMaxSize = 65532 / 3;
+ this->lineObjMaxSize = 65534 / 2;
+ this->Internal = new vtkInternal();
+ this->TriangleFilter = nullptr;
+ this->GradientBackground = false;
+ this->SetCenterOfRotation(0.0, 0.0, 0.0);
+ this->renderersMetaData = "";
+ this->SceneSize[0] = 0;
+ this->SceneSize[1] = 0;
+ this->SceneSize[2] = 0;
+ this->hasWidget = false;
+}
+
+vtkWebGLExporter::~vtkWebGLExporter()
+{
+ while (!this->Internal->Objects.empty())
+ {
+ vtkWebGLObject* obj = this->Internal->Objects.back();
+ obj->Delete();
+ this->Internal->Objects.pop_back();
+ }
+ delete this->Internal;
+ if (this->TriangleFilter)
+ {
+ this->TriangleFilter->Delete();
+ }
+}
+
+void vtkWebGLExporter::SetMaxAllowedSize(int mesh, int lines)
+{
+ this->meshObjMaxSize = mesh;
+ this->lineObjMaxSize = lines;
+ if (this->meshObjMaxSize * 3 > 65532)
+ this->meshObjMaxSize = 65532 / 3;
+ if (this->lineObjMaxSize * 2 > 65534)
+ this->lineObjMaxSize = 65534 / 2;
+ if (this->meshObjMaxSize < 10)
+ this->meshObjMaxSize = 10;
+ if (this->lineObjMaxSize < 10)
+ this->lineObjMaxSize = 10;
+ for (size_t i = 0; i < this->Internal->Objects.size(); i++)
+ this->Internal->Objects[i]->GenerateBinaryData();
+}
+
+void vtkWebGLExporter::SetMaxAllowedSize(int size)
+{
+ this->SetMaxAllowedSize(size, size);
+}
+
+void vtkWebGLExporter::SetCenterOfRotation(float a1, float a2, float a3)
+{
+ this->CenterOfRotation[0] = a1;
+ this->CenterOfRotation[1] = a2;
+ this->CenterOfRotation[2] = a3;
+}
+
+void vtkWebGLExporter::parseRenderer(
+ vtkRenderer* renderer, const char* vtkNotUsed(viewId), bool onlyWidget, void* vtkNotUsed(mapTime))
+{
+ vtkPropCollection* propCollection = renderer->GetViewProps();
+ for (int i = 0; i < propCollection->GetNumberOfItems(); i++)
+ {
+ vtkProp* prop = (vtkProp*)propCollection->GetItemAsObject(i);
+ vtkWidgetRepresentation* trt = vtkWidgetRepresentation::SafeDownCast(prop);
+ if (trt != nullptr)
+ this->hasWidget = true;
+ if ((!onlyWidget || trt != nullptr) && prop->GetVisibility())
+ {
+ vtkPropCollection* allactors = vtkPropCollection::New();
+ prop->GetActors(allactors);
+ for (int j = 0; j < allactors->GetNumberOfItems(); j++)
+ {
+ vtkActor* actor = vtkActor::SafeDownCast(allactors->GetItemAsObject(j));
+ vtkActor* key = actor;
+ vtkMTimeType previousValue = this->Internal->OldActorTimestamp[key];
+ this->parseActor(
+ actor, previousValue, (size_t)renderer, renderer->GetLayer(), trt != nullptr);
+ }
+ allactors->Delete();
+ }
+ if (!onlyWidget && prop->GetVisibility())
+ {
+ vtkPropCollection* all2dactors = vtkPropCollection::New();
+ prop->GetActors2D(all2dactors);
+ for (int k = 0; k < all2dactors->GetNumberOfItems(); k++)
+ {
+ vtkActor2D* actor = vtkActor2D::SafeDownCast(all2dactors->GetItemAsObject(k));
+ vtkActor2D* key = actor;
+ vtkMTimeType previousValue = this->Internal->OldActorTimestamp[key];
+ this->parseActor2D(
+ actor, previousValue, (size_t)renderer, renderer->GetLayer(), trt != nullptr);
+ }
+ all2dactors->Delete();
+ }
+ }
+}
+
+void vtkWebGLExporter::parseActor2D(
+ vtkActor2D* actor, vtkMTimeType actorTime, size_t renderId, int layer, bool isWidget)
+{
+ vtkActor2D* key = actor;
+ vtkScalarBarActor* scalarbar = vtkScalarBarActor::SafeDownCast(actor);
+
+ vtkMTimeType dataMTime =
+ actor->GetMTime() + actor->GetRedrawMTime() + actor->GetProperty()->GetMTime();
+ dataMTime += (vtkMTimeType)actor->GetMapper();
+ if (scalarbar)
+ dataMTime += scalarbar->GetLookupTable()->GetMTime();
+ if (dataMTime != actorTime && actor->GetVisibility())
+ {
+ this->Internal->ActorTimestamp[key] = dataMTime;
+
+ if (actor->GetMapper())
+ {
+ if (vtkPolyDataMapper2D::SafeDownCast(actor->GetMapper()))
+ {
+ }
+ }
+ else
+ {
+ if (scalarbar)
+ {
+ vtkWebGLWidget* obj = vtkWebGLWidget::New();
+ obj->GetDataFromColorMap(actor);
+
+ std::stringstream ss;
+ ss << (size_t)actor;
+ obj->SetId(ss.str());
+ obj->SetRendererId(static_cast<int>(renderId));
+ this->Internal->Objects.push_back(obj);
+ obj->SetLayer(layer);
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ obj->SetIsWidget(isWidget);
+ obj->SetInteractAtServer(false);
+ obj->GenerateBinaryData();
+ }
+ }
+ }
+ else
+ {
+ this->Internal->ActorTimestamp[key] = dataMTime;
+ std::stringstream ss;
+ ss << (vtkMTimeType)actor;
+ for (size_t i = 0; i < this->Internal->tempObj.size(); i++)
+ {
+ if (this->Internal->tempObj[i]->GetId() == ss.str())
+ {
+ vtkWebGLObject* obj = this->Internal->tempObj[i];
+ this->Internal->tempObj.erase(this->Internal->tempObj.begin() + i);
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ this->Internal->Objects.push_back(obj);
+ }
+ }
+ }
+}
+
+void vtkWebGLExporter::parseActor(
+ vtkActor* actor, vtkMTimeType actorTime, size_t rendererId, int layer, bool isWidget)
+{
+ vtkMapper* mapper = actor->GetMapper();
+ if (mapper)
+ {
+ vtkMTimeType dataMTime;
+ vtkTriangleFilter* polydata = this->GetPolyData(mapper, dataMTime);
+ vtkActor* key = actor;
+ dataMTime = actor->GetMTime() + mapper->GetLookupTable()->GetMTime();
+ dataMTime += actor->GetProperty()->GetMTime() + mapper->GetMTime() + actor->GetRedrawMTime();
+ dataMTime +=
+ polydata->GetOutput()->GetNumberOfLines() + polydata->GetOutput()->GetNumberOfPolys();
+ dataMTime +=
+ actor->GetProperty()->GetRepresentation() + mapper->GetScalarMode() + actor->GetVisibility();
+ dataMTime += polydata->GetInput()->GetMTime();
+ if (vtkFollower::SafeDownCast(actor))
+ dataMTime += vtkFollower::SafeDownCast(actor)->GetCamera()->GetMTime();
+ if (dataMTime != actorTime && actor->GetVisibility())
+ {
+ double bb[6];
+ actor->GetBounds(bb);
+ double m1 = std::max(bb[1] - bb[0], bb[3] - bb[2]);
+ m1 = std::max(m1, bb[5] - bb[4]);
+ double m2 = std::max(this->SceneSize[0], this->SceneSize[1]);
+ m2 = std::max(m2, this->SceneSize[2]);
+ if (m1 > m2)
+ {
+ this->SceneSize[0] = bb[1] - bb[0];
+ this->SceneSize[1] = bb[3] - bb[2];
+ this->SceneSize[2] = bb[5] - bb[4];
+ }
+
+ this->Internal->ActorTimestamp[key] = dataMTime;
+ vtkWebGLObject* obj = nullptr;
+ std::stringstream ss;
+ ss << (size_t)actor;
+ for (size_t i = 0; i < this->Internal->tempObj.size(); i++)
+ {
+ if (this->Internal->tempObj[i]->GetId() == ss.str())
+ {
+ obj = this->Internal->tempObj[i];
+ this->Internal->tempObj.erase(this->Internal->tempObj.begin() + i);
+ }
+ }
+ if (obj == nullptr)
+ obj = vtkWebGLPolyData::New();
+
+ if (polydata->GetOutput()->GetNumberOfPolys() != 0)
+ {
+ if (actor->GetProperty()->GetRepresentation() == VTK_WIREFRAME)
+ {
+ ((vtkWebGLPolyData*)obj)
+ ->GetLinesFromPolygon(mapper, actor, this->lineObjMaxSize, nullptr);
+ }
+ else
+ {
+
+ if (actor->GetProperty()->GetEdgeVisibility())
+ {
+ vtkWebGLPolyData* newobj = vtkWebGLPolyData::New();
+ double ccc[3];
+ actor->GetProperty()->GetEdgeColor(&ccc[0]);
+ newobj->GetLinesFromPolygon(mapper, actor, this->lineObjMaxSize, ccc);
+ newobj->SetId(ss.str() + "1");
+ newobj->SetRendererId(static_cast<int>(rendererId));
+ this->Internal->Objects.push_back(newobj);
+ newobj->SetLayer(layer);
+ newobj->SetTransformationMatrix(actor->GetMatrix());
+ newobj->SetVisibility(actor->GetVisibility() != 0);
+ newobj->SetHasTransparency(actor->HasTranslucentPolygonalGeometry() != 0);
+ newobj->SetIsWidget(isWidget);
+ newobj->SetInteractAtServer(isWidget);
+ newobj->GenerateBinaryData();
+ }
+
+ switch (mapper->GetScalarMode())
+ {
+ case VTK_SCALAR_MODE_USE_POINT_FIELD_DATA:
+ ((vtkWebGLPolyData*)obj)
+ ->GetPolygonsFromPointData(polydata, actor, this->meshObjMaxSize);
+ break;
+ case VTK_SCALAR_MODE_USE_CELL_FIELD_DATA:
+ ((vtkWebGLPolyData*)obj)
+ ->GetPolygonsFromCellData(polydata, actor, this->meshObjMaxSize);
+ break;
+ default:
+ ((vtkWebGLPolyData*)obj)
+ ->GetPolygonsFromPointData(polydata, actor, this->meshObjMaxSize);
+ break;
+ }
+ }
+ obj->SetId(ss.str());
+ obj->SetRendererId(static_cast<int>(rendererId));
+ this->Internal->Objects.push_back(obj);
+ obj->SetLayer(layer);
+ obj->SetTransformationMatrix(actor->GetMatrix());
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ obj->SetHasTransparency(actor->HasTranslucentPolygonalGeometry() != 0);
+ obj->SetIsWidget(isWidget);
+ obj->SetInteractAtServer(isWidget);
+ obj->GenerateBinaryData();
+ }
+ else if (polydata->GetOutput()->GetNumberOfLines() != 0)
+ {
+ ((vtkWebGLPolyData*)obj)->GetLines(polydata, actor, this->lineObjMaxSize);
+ obj->SetId(ss.str());
+ obj->SetRendererId(static_cast<int>(rendererId));
+ this->Internal->Objects.push_back(obj);
+ obj->SetLayer(layer);
+ obj->SetTransformationMatrix(actor->GetMatrix());
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ obj->SetHasTransparency(actor->HasTranslucentPolygonalGeometry() != 0);
+ obj->SetIsWidget(isWidget);
+ obj->SetInteractAtServer(isWidget);
+ obj->GenerateBinaryData();
+ }
+ else if (polydata->GetOutput()->GetNumberOfPoints() != 0)
+ {
+ ((vtkWebGLPolyData*)obj)->GetPoints(polydata, actor, 65534); // Wendel
+ obj->SetId(ss.str());
+ obj->SetRendererId(static_cast<int>(rendererId));
+ this->Internal->Objects.push_back(obj);
+ obj->SetLayer(layer);
+ obj->SetTransformationMatrix(actor->GetMatrix());
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ obj->SetHasTransparency(actor->HasTranslucentPolygonalGeometry() != 0);
+ obj->SetIsWidget(false);
+ obj->SetInteractAtServer(false);
+ obj->GenerateBinaryData();
+ }
+
+ if (polydata->GetOutput()->GetNumberOfPolys() != 0 &&
+ polydata->GetOutput()->GetNumberOfLines() != 0)
+ {
+ obj = vtkWebGLPolyData::New();
+ ((vtkWebGLPolyData*)obj)->GetLines(polydata, actor, this->lineObjMaxSize);
+ ss << "1";
+ obj->SetId(ss.str());
+ obj->SetRendererId(static_cast<int>(rendererId));
+ this->Internal->Objects.push_back(obj);
+ obj->SetLayer(layer);
+ obj->SetTransformationMatrix(actor->GetMatrix());
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ obj->SetHasTransparency(actor->HasTranslucentPolygonalGeometry() != 0);
+ obj->SetIsWidget(isWidget);
+ obj->SetInteractAtServer(isWidget);
+ obj->GenerateBinaryData();
+ }
+
+ if (polydata->GetOutput()->GetNumberOfLines() == 0 &&
+ polydata->GetOutput()->GetNumberOfPolys() == 0 &&
+ polydata->GetOutput()->GetNumberOfPoints() == 0)
+ {
+ obj->Delete();
+ }
+ }
+ else
+ {
+ this->Internal->ActorTimestamp[key] = actorTime;
+ std::stringstream ss;
+ ss << (size_t)actor;
+ for (size_t i = 0; i < this->Internal->tempObj.size(); i++)
+ {
+ if (this->Internal->tempObj[i]->GetId() == ss.str())
+ {
+ vtkWebGLObject* obj = this->Internal->tempObj[i];
+ this->Internal->tempObj.erase(this->Internal->tempObj.begin() + i);
+ obj->SetVisibility(actor->GetVisibility() != 0);
+ this->Internal->Objects.push_back(obj);
+ }
+ }
+ }
+ }
+}
+
+void vtkWebGLExporter::parseScene(
+ vtkRendererCollection* renderers, const char* viewId, int parseType)
+{
+ if (!renderers)
+ return;
+
+ bool onlyWidget = parseType == VTK_ONLYWIDGET;
+ bool cameraOnly = onlyWidget && !this->hasWidget;
+
+ this->SceneId = viewId ? viewId : "";
+ if (cameraOnly)
+ {
+ this->generateRendererData(renderers, viewId);
+ return;
+ }
+
+ if (onlyWidget)
+ {
+ for (int i = static_cast<int>(this->Internal->Objects.size()) - 1; i >= 0; i--)
+ {
+ vtkWebGLObject* obj = this->Internal->Objects[i];
+ if (obj->InteractAtServer())
+ {
+ this->Internal->tempObj.push_back(obj);
+ this->Internal->Objects.erase(this->Internal->Objects.begin() + i);
+ }
+ }
+ }
+ else
+ {
+ while (!this->Internal->Objects.empty())
+ {
+ this->Internal->tempObj.push_back(this->Internal->Objects.back());
+ this->Internal->Objects.pop_back();
+ }
+ }
+
+ this->Internal->OldActorTimestamp = this->Internal->ActorTimestamp;
+ if (!onlyWidget)
+ this->Internal->ActorTimestamp.clear();
+ this->hasWidget = false;
+ for (int i = 0; i < renderers->GetNumberOfItems(); i++)
+ {
+ vtkRenderer* renderer = vtkRenderer::SafeDownCast(renderers->GetItemAsObject(i));
+ if (renderer->GetDraw())
+ this->parseRenderer(renderer, viewId, onlyWidget, nullptr);
+ }
+ while (!this->Internal->tempObj.empty())
+ {
+ vtkWebGLObject* obj = this->Internal->tempObj.back();
+ this->Internal->tempObj.pop_back();
+ obj->Delete();
+ }
+
+ this->generateRendererData(renderers, viewId);
+}
+
+bool sortLayer(vtkRenderer* i, vtkRenderer* j)
+{
+ return (i->GetLayer() < j->GetLayer());
+}
+
+void vtkWebGLExporter::generateRendererData(
+ vtkRendererCollection* renderers, const char* vtkNotUsed(viewId))
+{
+ std::stringstream ss;
+ ss << "\"Renderers\": [";
+
+ std::vector<vtkRenderer*> orderedList;
+ orderedList.reserve(renderers->GetNumberOfItems());
+ for (int i = 0; i < renderers->GetNumberOfItems(); i++)
+ orderedList.push_back(vtkRenderer::SafeDownCast(renderers->GetItemAsObject(i)));
+ std::sort(orderedList.begin(), orderedList.begin() + orderedList.size(), sortLayer);
+
+ int* fullSize = nullptr;
+ for (size_t i = 0; i < orderedList.size(); i++)
+ {
+ vtkRenderer* renderer = orderedList[i];
+
+ if (i == 0)
+ {
+ fullSize = renderer->GetSize();
+ }
+
+ double cam[10];
+ cam[0] = renderer->GetActiveCamera()->GetViewAngle();
+ renderer->GetActiveCamera()->GetFocalPoint(&cam[1]);
+ renderer->GetActiveCamera()->GetViewUp(&cam[4]);
+ renderer->GetActiveCamera()->GetPosition(&cam[7]);
+ int *s, *o;
+ s = renderer->GetSize();
+ o = renderer->GetOrigin();
+ ss << "{\"layer\":" << renderer->GetLayer() << ","; // Render Layer
+ if (renderer->GetLayer() == 0) // Render Background
+ {
+ double back[3];
+ renderer->GetBackground(back);
+ ss << "\"Background1\":[" << back[0] << "," << back[1] << "," << back[2] << "],";
+ if (renderer->GetGradientBackground())
+ {
+ renderer->GetBackground2(back);
+ ss << "\"Background2\":[" << back[0] << "," << back[1] << "," << back[2] << "],";
+ }
+ }
+ ss << "\"LookAt\":["; // Render Camera
+ for (int j = 0; j < 9; j++)
+ ss << cam[j] << ",";
+ ss << cam[9] << "], ";
+ ss << "\"size\": [" << (float)(s[0] / (float)fullSize[0]) << ","
+ << (float)(s[1] / (float)fullSize[1]) << "],"; // Render Size
+ ss << "\"origin\": [" << (float)(o[0] / (float)fullSize[0]) << ","
+ << (float)(o[1] / (float)fullSize[1]) << "]"; // Render Position
+ ss << "}";
+ if (static_cast<int>(i + 1) != renderers->GetNumberOfItems())
+ ss << ", ";
+ }
+ ss << "]";
+ this->renderersMetaData = ss.str();
+}
+
+vtkTriangleFilter* vtkWebGLExporter::GetPolyData(vtkMapper* mapper, vtkMTimeType& dataMTime)
+{
+ vtkDataSet* dataset = nullptr;
+ vtkSmartPointer<vtkDataSet> tempDS;
+ vtkDataObject* dObj = mapper->GetInputDataObject(0, 0);
+ vtkCompositeDataSet* cd = vtkCompositeDataSet::SafeDownCast(dObj);
+ if (cd)
+ {
+ dataMTime = cd->GetMTime();
+ vtkCompositeDataGeometryFilter* gf = vtkCompositeDataGeometryFilter::New();
+ gf->SetInputData(cd);
+ gf->Update();
+ tempDS = gf->GetOutput();
+ gf->Delete();
+ dataset = tempDS;
+ }
+ else
+ {
+ dataset = mapper->GetInput();
+ dataMTime = dataset->GetMTime();
+ }
+
+ // Converting to triangles. WebGL only support triangles.
+ if (this->TriangleFilter)
+ this->TriangleFilter->Delete();
+ this->TriangleFilter = vtkTriangleFilter::New();
+ this->TriangleFilter->SetInputData(dataset);
+ this->TriangleFilter->Update();
+ return this->TriangleFilter;
+}
+
+/*
+ Function: GenerateMetaData
+ Description:
+ - Generates the metadata of the scene in JSON format
+ Ex.:
+ { "id": ,"LookAt": ,"Background1": ,"Background2":
+ "Objects": [{"id": ,"md5": ,"parts": }, {"id": ,"md5": ,"parts": }] }
+*/
+VTK_ABI_NAMESPACE_BEGIN
+const char* vtkWebGLExporter::GenerateMetadata()
+{
+ double max = std::max(this->SceneSize[0], this->SceneSize[1]);
+ max = std::max(max, this->SceneSize[2]);
+ std::stringstream ss;
+
+ ss << "{\"id\":" << this->SceneId << ",";
+ ss << "\"MaxSize\":" << max << ",";
+ ss << "\"Center\":[";
+ for (int i = 0; i < 2; i++)
+ ss << this->CenterOfRotation[i] << ", ";
+ ss << this->CenterOfRotation[2] << "],";
+
+ ss << this->renderersMetaData << ",";
+
+ ss << " \"Objects\":[";
+ bool first = true;
+ for (size_t i = 0; i < this->Internal->Objects.size(); i++)
+ {
+ vtkWebGLObject* obj = this->Internal->Objects[i];
+ if (obj->isVisible())
+ {
+ if (first)
+ first = false;
+ else
+ ss << ", ";
+ ss << "{\"id\":" << obj->GetId() << ", \"md5\":\"" << obj->GetMD5() << "\""
+ << ", \"parts\":" << obj->GetNumberOfParts()
+ << ", \"interactAtServer\":" << obj->InteractAtServer()
+ << ", \"transparency\":" << obj->HasTransparency() << ", \"layer\":" << obj->GetLayer()
+ << ", \"wireframe\":" << obj->isWireframeMode() << "}";
+ }
+ }
+ ss << "]}";
+
+ this->Internal->LastMetaData = ss.str();
+ return this->Internal->LastMetaData.c_str();
+}
+
+const char* vtkWebGLExporter::GenerateExportMetadata()
+{
+ double max = std::max(this->SceneSize[0], this->SceneSize[1]);
+ max = std::max(max, this->SceneSize[2]);
+ std::stringstream ss;
+
+ ss << "{\"id\":" << this->SceneId << ",";
+ ss << "\"MaxSize\":" << max << ",";
+ ss << "\"Center\":[";
+ for (int i = 0; i < 2; i++)
+ ss << this->CenterOfRotation[i] << ", ";
+ ss << this->CenterOfRotation[2] << "],";
+
+ ss << this->renderersMetaData << ",";
+
+ ss << " \"Objects\":[";
+ bool first = true;
+ for (size_t i = 0; i < this->Internal->Objects.size(); i++)
+ {
+ vtkWebGLObject* obj = this->Internal->Objects[i];
+ if (obj->isVisible())
+ {
+ for (int j = 0; j < obj->GetNumberOfParts(); j++)
+ {
+ if (first)
+ first = false;
+ else
+ ss << ", ";
+ ss << "{\"id\":" << obj->GetId() << ", \"md5\":\"" << obj->GetMD5() << "\""
+ << ", \"parts\":" << 1 << ", \"interactAtServer\":" << obj->InteractAtServer()
+ << ", \"transparency\":" << obj->HasTransparency() << ", \"layer\":" << obj->GetLayer()
+ << ", \"wireframe\":" << obj->isWireframeMode() << "}";
+ }
+ }
+ }
+ ss << "]}";
+
+ this->Internal->LastMetaData = ss.str();
+ return this->Internal->LastMetaData.c_str();
+}
+
+vtkWebGLObject* vtkWebGLExporter::GetWebGLObject(int index)
+{
+ return this->Internal->Objects[index];
+}
+
+int vtkWebGLExporter::GetNumberOfObjects()
+{
+ return static_cast<int>(this->Internal->Objects.size());
+}
+
+void vtkWebGLExporter::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+const char* vtkWebGLExporter::GetId()
+{
+ return this->SceneId.c_str();
+}
+
+bool vtkWebGLExporter::hasChanged()
+{
+ for (size_t i = 0; i < this->Internal->Objects.size(); i++)
+ if (this->Internal->Objects[i]->HasChanged())
+ return true;
+ return false;
+}
+
+void vtkWebGLExporter::exportStaticScene(
+ vtkRendererCollection* renderers, int width, int height, std::string path)
+{
+ std::stringstream ss;
+ ss << width << "," << height;
+ std::string resultHTML =
+ "<html><head></head><body onload='loadStaticScene();' style='margin: 0px; padding: 0px; "
+ "position: absolute; overflow: hidden; top:0px; left:0px;'>";
+ resultHTML += "<div id='container' onclick='consumeEvent(event);' style='margin: 0px; padding: "
+ "0px; position: absolute; overflow: hidden; top:0px; left:0px;'></div></body>\n";
+ resultHTML += "<script type='text/javascript'> var rendererWebGL = null;";
+ resultHTML += "function reresize(event){ if (rendererWebGL != null) "
+ "rendererWebGL.setSize(window.innerWidth, window.innerHeight); }";
+ resultHTML += "function loadStaticScene(){ ";
+ resultHTML += " var objs=[];";
+ resultHTML += " for(i=0; i<object.length; i++){";
+ resultHTML += " objs[i] = decode64(object[i]);";
+ resultHTML += " }\n object = [];";
+ resultHTML += " rendererWebGL = new WebGLRenderer('webglRenderer-1', '');";
+ resultHTML += " rendererWebGL.init('', '');";
+ resultHTML += " rendererWebGL.bindToElementId('container');";
+ resultHTML += " //rendererWebGL.setSize(" + ss.str() + ");\n";
+ resultHTML += " rendererWebGL.setSize(window.innerWidth, window.innerHeight);";
+ resultHTML += " rendererWebGL.start(metadata, objs);";
+ resultHTML += " window.onresize = reresize;";
+ resultHTML += "}\n";
+ resultHTML += "function consumeEvent(event) { if (event.preventDefault) { "
+ "event.preventDefault();} else { event.returnValue= false;} return false;}";
+
+ resultHTML += "function ntos(n){ n=n.toString(16); if (n.length == 1) n='0'+n; n='%'+n; return "
+ "unescape(n); }";
+ resultHTML += "var END_OF_INPUT = -1; var base64Chars = new Array(";
+ resultHTML += "'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','"
+ "U','V','W','X',";
+ resultHTML += "'Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','"
+ "s','t','u','v',";
+ resultHTML += "'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/');";
+ resultHTML += "var base64Str; var base64Count;";
+ resultHTML += "var reverseBase64Chars = new Array();";
+ resultHTML +=
+ "for (var i=0; i < base64Chars.length; i++){ reverseBase64Chars[base64Chars[i]] = i; }";
+ resultHTML += "function readReverseBase64(){ if (!base64Str) return END_OF_INPUT;";
+ resultHTML += "while (true){ if (base64Count >= base64Str.length) return END_OF_INPUT;";
+ resultHTML += "var nextCharacter = base64Str.charAt(base64Count); base64Count++;";
+ resultHTML +=
+ "if (reverseBase64Chars[nextCharacter]){ return reverseBase64Chars[nextCharacter]; }";
+ resultHTML += "if (nextCharacter == 'A') return 0; } return END_OF_INPUT; }";
+ resultHTML += "function decode64(str){";
+ resultHTML += "base64Str = str; base64Count = 0; var result = ''; var inBuffer = new Array(4); "
+ "var done = false;";
+ resultHTML += "while (!done && (inBuffer[0] = readReverseBase64()) != END_OF_INPUT";
+ resultHTML += "&& (inBuffer[1] = readReverseBase64()) != END_OF_INPUT){";
+ resultHTML += "inBuffer[2] = readReverseBase64();";
+ resultHTML += "inBuffer[3] = readReverseBase64();";
+ resultHTML += "result += ntos((((inBuffer[0] << 2) & 0xff)| inBuffer[1] >> 4));";
+ resultHTML += "if (inBuffer[2] != END_OF_INPUT){";
+ resultHTML += "result += ntos((((inBuffer[1] << 4) & 0xff)| inBuffer[2] >> 2));";
+ resultHTML += "if (inBuffer[3] != END_OF_INPUT){";
+ resultHTML += "result += ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3]));";
+ resultHTML += "} else { done = true; }";
+ resultHTML += "} else { done = true; } }";
+ resultHTML += "return result; }";
+
+ this->parseScene(renderers, "1234567890", VTK_PARSEALL);
+ const char* metadata = this->GenerateExportMetadata();
+ resultHTML += "var metadata = '" + std::string(metadata) + "';";
+ resultHTML += "var object = [";
+ for (int i = 0; i < this->GetNumberOfObjects(); i++)
+ {
+ std::string test;
+ int size = 0;
+
+ vtkWebGLObject* obj = this->GetWebGLObject(i);
+ if (obj->isVisible())
+ {
+ for (int j = 0; j < obj->GetNumberOfParts(); j++)
+ {
+ unsigned char* output = new unsigned char[obj->GetBinarySize(j) * 2];
+ size =
+ vtkBase64Utilities::Encode(obj->GetBinaryData(j), obj->GetBinarySize(j), output, false);
+ test = std::string((const char*)output, size);
+ resultHTML += "'" + test + "',\n";
+ delete[] output;
+ }
+ }
+ }
+ resultHTML += "''];";
+
+ resultHTML += webglRenderer;
+ resultHTML += glMatrix;
+
+ resultHTML += "</script></html>";
+
+ vtksys::ofstream file;
+ file.open(path.c_str());
+ file << resultHTML;
+ file.close();
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLExporter::ComputeMD5(const unsigned char* content, int size, std::string& hash)
+{
+ unsigned char digest[16];
+ char md5Hash[33];
+ md5Hash[32] = '\0';
+
+ vtksysMD5* md5 = vtksysMD5_New();
+ vtksysMD5_Initialize(md5);
+ vtksysMD5_Append(md5, content, size);
+ vtksysMD5_Finalize(md5, digest);
+ vtksysMD5_DigestToHex(digest, md5Hash);
+ vtksysMD5_Delete(md5);
+
+ hash = md5Hash;
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebGLExporter
+ * @brief vtkWebGLExporter export the data of the scene to be used in the WebGL.
+ */
+
+#ifndef vtkWebGLExporter_h
+#define vtkWebGLExporter_h
+
+#include "vtkObject.h"
+#include "vtkWebGLExporterModule.h" // needed for export macro
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkActor;
+class vtkActor2D;
+class vtkCellData;
+class vtkMapper;
+class vtkPointData;
+class vtkPolyData;
+class vtkRenderer;
+class vtkRendererCollection;
+class vtkTriangleFilter;
+class vtkWebGLObject;
+class vtkWebGLPolyData;
+
+VTK_ABI_NAMESPACE_END
+
+#include <string> // needed for internal structure
+
+VTK_ABI_NAMESPACE_BEGIN
+typedef enum
+{
+ VTK_ONLYCAMERA = 0,
+ VTK_ONLYWIDGET = 1,
+ VTK_PARSEALL = 2
+} VTKParseType;
+
+class VTKWEBGLEXPORTER_EXPORT vtkWebGLExporter : public vtkObject
+{
+public:
+ static vtkWebGLExporter* New();
+ vtkTypeMacro(vtkWebGLExporter, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ ///@{
+ /**
+ * Get all the needed information from the vtkRenderer
+ */
+ void parseScene(vtkRendererCollection* renderers, const char* viewId, int parseType);
+ // Generate and return the Metadata
+ void exportStaticScene(vtkRendererCollection* renderers, int width, int height, std::string path);
+ const char* GenerateMetadata();
+ const char* GetId();
+ vtkWebGLObject* GetWebGLObject(int index);
+ int GetNumberOfObjects();
+ bool hasChanged();
+ void SetCenterOfRotation(float a1, float a2, float a3);
+ void SetMaxAllowedSize(int mesh, int lines);
+ void SetMaxAllowedSize(int size);
+ ///@}
+
+ static void ComputeMD5(const unsigned char* content, int size, std::string& hash);
+
+protected:
+ vtkWebGLExporter();
+ ~vtkWebGLExporter() override;
+
+ void parseRenderer(vtkRenderer* render, const char* viewId, bool onlyWidget, void* mapTime);
+ void generateRendererData(vtkRendererCollection* renderers, const char* viewId);
+ void parseActor(
+ vtkActor* actor, vtkMTimeType actorTime, size_t rendererId, int layer, bool isWidget);
+ void parseActor2D(
+ vtkActor2D* actor, vtkMTimeType actorTime, size_t renderId, int layer, bool isWidget);
+ const char* GenerateExportMetadata();
+
+ // Get the dataset from the mapper
+ vtkTriangleFilter* GetPolyData(vtkMapper* mapper, vtkMTimeType& dataMTime);
+
+ vtkTriangleFilter* TriangleFilter; // Last Polygon Dataset Parse
+ double CameraLookAt[10]; // Camera Look At (fov, position[3], up[3], eye[3])
+ bool GradientBackground; // If the scene use a gradient background
+ double Background1[3]; // Background color of the rendering screen (RGB)
+ double Background2[3]; // Second background color
+ double SceneSize[3]; // Size of the bounding box of the scene
+ std::string SceneId; // Id of the parsed scene
+ float CenterOfRotation[3]; // Center Of Rotation
+ int meshObjMaxSize, lineObjMaxSize; // Max size of object allowed (faces)
+ std::string renderersMetaData;
+ bool hasWidget;
+
+private:
+ vtkWebGLExporter(const vtkWebGLExporter&) = delete;
+ void operator=(const vtkWebGLExporter&) = delete;
+
+ class vtkInternal;
+ vtkInternal* Internal;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "vtkWebGLObject.h"
+
+#include "vtkMatrix4x4.h"
+#include "vtkObjectFactory.h"
+#include "vtkUnsignedCharArray.h"
+
+#include <algorithm>
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkWebGLObject);
+VTK_ABI_NAMESPACE_END
+#include <map>
+#include <vector>
+
+//------------------------------------------------------------------------------
+VTK_ABI_NAMESPACE_BEGIN
+vtkWebGLObject::vtkWebGLObject()
+{
+ this->iswireframeMode = false;
+ this->hasChanged = false;
+ this->webGlType = wTRIANGLES;
+ this->hasTransparency = false;
+ this->iswidget = false;
+ this->interactAtServer = false;
+}
+
+//------------------------------------------------------------------------------
+vtkWebGLObject::~vtkWebGLObject() = default;
+
+//------------------------------------------------------------------------------
+std::string vtkWebGLObject::GetId()
+{
+ return this->id;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetId(const std::string& i)
+{
+ this->id = i;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetType(WebGLObjectTypes t)
+{
+ this->webGlType = t;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetTransformationMatrix(vtkMatrix4x4* m)
+{
+ for (int i = 0; i < 16; i++)
+ this->Matrix[i] = m->GetElement(i / 4, i % 4);
+}
+
+//------------------------------------------------------------------------------
+std::string vtkWebGLObject::GetMD5()
+{
+ return this->MD5;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebGLObject::HasChanged()
+{
+ return this->hasChanged;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetWireframeMode(bool wireframe)
+{
+ this->iswireframeMode = wireframe;
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebGLObject::isWireframeMode()
+{
+ return this->iswireframeMode;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetVisibility(bool vis)
+{
+ this->isvisible = vis;
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebGLObject::isVisible()
+{
+ return this->isvisible;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetHasTransparency(bool t)
+{
+ this->hasTransparency = t;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetIsWidget(bool w)
+{
+ this->iswidget = w;
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebGLObject::isWidget()
+{
+ return this->iswidget;
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebGLObject::HasTransparency()
+{
+ return this->hasTransparency;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetRendererId(size_t i)
+{
+ this->rendererId = i;
+}
+
+//------------------------------------------------------------------------------
+size_t vtkWebGLObject::GetRendererId()
+{
+ return this->rendererId;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetLayer(int l)
+{
+ this->layer = l;
+}
+
+//------------------------------------------------------------------------------
+int vtkWebGLObject::GetLayer()
+{
+ return this->layer;
+}
+
+//------------------------------------------------------------------------------
+bool vtkWebGLObject::InteractAtServer()
+{
+ return this->interactAtServer;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::SetInteractAtServer(bool i)
+{
+ this->interactAtServer = i;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::GetBinaryData(int part, vtkUnsignedCharArray* buffer)
+{
+ if (!buffer)
+ {
+ vtkErrorMacro("Buffer must not be nullptr.");
+ return;
+ }
+
+ const int binarySize = this->GetBinarySize(part);
+ const unsigned char* binaryData = this->GetBinaryData(part);
+
+ buffer->SetNumberOfComponents(1);
+ buffer->SetNumberOfTuples(binarySize);
+
+ if (binarySize)
+ {
+ std::copy(binaryData, binaryData + binarySize, buffer->GetPointer(0));
+ }
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGLObject::GenerateBinaryData()
+{
+ this->hasChanged = false;
+}
+//------------------------------------------------------------------------------
+unsigned char* vtkWebGLObject::GetBinaryData(int vtkNotUsed(part))
+{
+ return nullptr;
+}
+//------------------------------------------------------------------------------
+int vtkWebGLObject::GetBinarySize(int vtkNotUsed(part))
+{
+ return 0;
+}
+//------------------------------------------------------------------------------
+int vtkWebGLObject::GetNumberOfParts()
+{
+ return 0;
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebGLObject
+ * @brief vtkWebGLObject represent and manipulate an WebGL object and its data.
+ */
+
+#ifndef vtkWebGLObject_h
+#define vtkWebGLObject_h
+
+#include "vtkObject.h"
+#include "vtkWebGLExporterModule.h" // needed for export macro
+
+#include <string> // needed for ID and md5 storing
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkMatrix4x4;
+class vtkUnsignedCharArray;
+
+enum WebGLObjectTypes
+{
+ wPOINTS = 0,
+ wLINES = 1,
+ wTRIANGLES = 2
+};
+
+class VTKWEBGLEXPORTER_EXPORT vtkWebGLObject : public vtkObject
+{
+public:
+ static vtkWebGLObject* New();
+ vtkTypeMacro(vtkWebGLObject, vtkObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ virtual void GenerateBinaryData();
+ virtual unsigned char* GetBinaryData(int part);
+ virtual int GetBinarySize(int part);
+ virtual int GetNumberOfParts();
+
+ /**
+ * This is a wrapper friendly method for access the binary data.
+ * The binary data for the requested part will be copied into the
+ * given vtkUnsignedCharArray.
+ */
+ void GetBinaryData(int part, vtkUnsignedCharArray* buffer);
+
+ void SetLayer(int l);
+ void SetRendererId(size_t i);
+ void SetId(const std::string& i);
+ void SetWireframeMode(bool wireframe);
+ void SetVisibility(bool vis);
+ void SetTransformationMatrix(vtkMatrix4x4* m);
+ void SetIsWidget(bool w);
+ void SetHasTransparency(bool t);
+ void SetInteractAtServer(bool i);
+ void SetType(WebGLObjectTypes t);
+ bool isWireframeMode();
+ bool isVisible();
+ bool HasChanged();
+ bool isWidget();
+ bool HasTransparency();
+ bool InteractAtServer();
+
+ std::string GetMD5();
+ std::string GetId();
+
+ size_t GetRendererId();
+ int GetLayer();
+
+protected:
+ vtkWebGLObject();
+ ~vtkWebGLObject() override;
+
+ float Matrix[16];
+ size_t rendererId;
+ int layer; // Renderer Layer
+ std::string id; // Id of the object
+ std::string MD5;
+ bool hasChanged;
+ bool iswireframeMode;
+ bool isvisible;
+ WebGLObjectTypes webGlType;
+ bool hasTransparency;
+ bool iswidget;
+ bool interactAtServer;
+
+private:
+ vtkWebGLObject(const vtkWebGLObject&) = delete;
+ void operator=(const vtkWebGLObject&) = delete;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "vtkWebGLPolyData.h"
+
+#include "vtkActor.h"
+#include "vtkCell.h"
+#include "vtkCellArray.h"
+#include "vtkCellData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkGenericCell.h"
+#include "vtkIdTypeArray.h"
+#include "vtkMapper.h"
+#include "vtkMatrix4x4.h"
+#include "vtkObjectFactory.h"
+#include "vtkPointData.h"
+#include "vtkPoints.h"
+#include "vtkPolyDataNormals.h"
+#include "vtkProperty.h"
+#include "vtkScalarsToColors.h"
+#include "vtkSmartPointer.h"
+#include "vtkTriangleFilter.h"
+#include "vtkUnsignedCharArray.h"
+#include "vtkWebGLDataSet.h"
+#include "vtkWebGLExporter.h"
+#include "vtkWebGLObject.h"
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkWebGLPolyData);
+//*****************************************************************************
+class vtkWebGLPolyData::vtkInternal
+{
+public:
+ std::vector<vtkWebGLDataSet*> Parts;
+ std::map<long int, short> IndexMap;
+};
+//*****************************************************************************
+
+vtkWebGLPolyData::vtkWebGLPolyData()
+{
+ this->webGlType = wTRIANGLES;
+ this->iswidget = false;
+ this->Internal = new vtkInternal();
+}
+
+vtkWebGLPolyData::~vtkWebGLPolyData()
+{
+ vtkWebGLDataSet* obj;
+ while (!this->Internal->Parts.empty())
+ {
+ obj = this->Internal->Parts.back();
+ this->Internal->Parts.pop_back();
+ obj->Delete();
+ }
+ delete this->Internal;
+}
+
+void vtkWebGLPolyData::SetMesh(float* _vertices, int _numberOfVertices, int* _index,
+ int _numberOfIndexes, float* _normals, unsigned char* _colors, float* _tcoords, int maxSize)
+{
+ this->webGlType = wTRIANGLES;
+
+ vtkWebGLDataSet* obj;
+ while (!this->Internal->Parts.empty())
+ {
+ obj = this->Internal->Parts.back();
+ this->Internal->Parts.pop_back();
+ obj->Delete();
+ }
+
+ short* index;
+ int div = maxSize * 3;
+ if (_numberOfVertices < div)
+ {
+ index = new short[_numberOfIndexes];
+ for (int i = 0; i < _numberOfIndexes; i++)
+ index[i] = (short)_index[i];
+
+ obj = vtkWebGLDataSet::New();
+ obj->SetVertices(_vertices, _numberOfVertices);
+ obj->SetIndexes(index, _numberOfIndexes);
+ obj->SetNormals(_normals);
+ obj->SetColors(_colors);
+ obj->SetMatrix(this->Matrix);
+ this->Internal->Parts.push_back(obj);
+ }
+ else
+ {
+ int total = _numberOfIndexes;
+ int curr = 0;
+ int size = 0;
+
+ while (curr < total)
+ {
+ if (div + curr > total)
+ size = total - curr;
+ else
+ size = div;
+
+ float* vertices = new float[size * 3];
+ float* normals = new float[size * 3];
+ unsigned char* colors = new unsigned char[size * 4];
+ short* indexes = new short[size];
+ float* tcoord = nullptr;
+ if (_tcoords)
+ tcoord = new float[size * 2];
+
+ this->Internal->IndexMap.clear();
+ int count = 0;
+ for (int j = 0; j < size; j++)
+ {
+ int ind = _index[curr + j];
+ if (this->Internal->IndexMap.find(ind) == this->Internal->IndexMap.end())
+ {
+ vertices[count * 3 + 0] = _vertices[ind * 3 + 0];
+ vertices[count * 3 + 1] = _vertices[ind * 3 + 1];
+ vertices[count * 3 + 2] = _vertices[ind * 3 + 2];
+
+ normals[count * 3 + 0] = _normals[ind * 3 + 0];
+ normals[count * 3 + 1] = _normals[ind * 3 + 1];
+ normals[count * 3 + 2] = _normals[ind * 3 + 2];
+
+ colors[count * 4 + 0] = _colors[ind * 4 + 0];
+ colors[count * 4 + 1] = _colors[ind * 4 + 1];
+ colors[count * 4 + 2] = _colors[ind * 4 + 2];
+ colors[count * 4 + 3] = _colors[ind * 4 + 3];
+
+ if (_tcoords)
+ {
+ tcoord[count * 2 + 0] = _tcoords[ind * 2 + 0];
+ tcoord[count * 2 + 1] = _tcoords[ind * 2 + 1];
+ }
+ this->Internal->IndexMap[ind] = count;
+ indexes[j] = count++;
+ }
+ else
+ {
+ indexes[j] = this->Internal->IndexMap[ind];
+ }
+ }
+ curr += size;
+ float* v = new float[count * 3];
+ memcpy(v, vertices, count * 3 * sizeof(float));
+ delete[] vertices;
+ float* n = new float[count * 3];
+ memcpy(n, normals, count * 3 * sizeof(float));
+ delete[] normals;
+ unsigned char* c = new unsigned char[count * 4];
+ memcpy(c, colors, count * 4);
+ delete[] colors;
+ obj = vtkWebGLDataSet::New();
+ obj->SetVertices(v, count);
+ obj->SetIndexes(indexes, size);
+ obj->SetNormals(n);
+ obj->SetColors(c);
+ if (_tcoords)
+ {
+ float* tc = new float[count * 2];
+ memcpy(tc, tcoord, count * 2 * sizeof(float));
+ delete[] tcoord;
+ obj->SetTCoords(tc);
+ }
+ obj->SetMatrix(this->Matrix);
+ this->Internal->Parts.push_back(obj);
+ }
+
+ delete[] _vertices;
+ delete[] _index;
+ delete[] _normals;
+ delete[] _colors;
+ delete[] _tcoords;
+ }
+}
+
+void vtkWebGLPolyData::SetLine(float* _points, int _numberOfPoints, int* _index, int _numberOfIndex,
+ unsigned char* _colors, int maxSize)
+{
+ this->webGlType = wLINES;
+
+ vtkWebGLDataSet* obj;
+ while (!this->Internal->Parts.empty())
+ {
+ obj = this->Internal->Parts.back();
+ this->Internal->Parts.pop_back();
+ obj->Delete();
+ }
+
+ short* index;
+ int div = maxSize * 2;
+ if (_numberOfPoints < div)
+ {
+ index = new short[_numberOfIndex];
+ for (int i = 0; i < _numberOfIndex; i++)
+ index[i] = (short)((unsigned int)_index[i]);
+ obj = vtkWebGLDataSet::New();
+ obj->SetPoints(_points, _numberOfPoints);
+ obj->SetIndexes(index, _numberOfIndex);
+ obj->SetColors(_colors);
+ obj->SetMatrix(this->Matrix);
+ this->Internal->Parts.push_back(obj);
+ }
+ else
+ {
+ int total = _numberOfIndex;
+ int curr = 0;
+ int size = 0;
+
+ while (curr < total)
+ {
+ if (div + curr > total)
+ size = total - curr;
+ else
+ size = div;
+
+ float* points = new float[size * 3];
+ unsigned char* colors = new unsigned char[size * 4];
+ short* indexes = new short[size];
+
+ for (int j = 0; j < size; j++)
+ {
+ indexes[j] = j;
+
+ points[j * 3 + 0] = _points[_index[curr + j] * 3 + 0];
+ points[j * 3 + 1] = _points[_index[curr + j] * 3 + 1];
+ points[j * 3 + 2] = _points[_index[curr + j] * 3 + 2];
+
+ colors[j * 4 + 0] = _colors[_index[curr + j] * 4 + 0];
+ colors[j * 4 + 1] = _colors[_index[curr + j] * 4 + 1];
+ colors[j * 4 + 2] = _colors[_index[curr + j] * 4 + 2];
+ colors[j * 4 + 3] = _colors[_index[curr + j] * 4 + 3];
+ }
+ curr += size;
+ obj = vtkWebGLDataSet::New();
+ obj->SetPoints(points, size);
+ obj->SetIndexes(indexes, size);
+ obj->SetColors(colors);
+ obj->SetMatrix(this->Matrix);
+ this->Internal->Parts.push_back(obj);
+ }
+ delete[] _points;
+ delete[] _index;
+ delete[] _colors;
+ }
+}
+
+void vtkWebGLPolyData::SetTransformationMatrix(vtkMatrix4x4* m)
+{
+ this->Superclass::SetTransformationMatrix(m);
+ for (size_t i = 0; i < this->Internal->Parts.size(); i++)
+ {
+ this->Internal->Parts[i]->SetMatrix(this->Matrix);
+ }
+}
+
+unsigned char* vtkWebGLPolyData::GetBinaryData(int part)
+{
+ this->hasChanged = false;
+ vtkWebGLDataSet* obj = this->Internal->Parts[part];
+ return obj->GetBinaryData();
+}
+
+int vtkWebGLPolyData::GetBinarySize(int part)
+{
+ vtkWebGLDataSet* obj = this->Internal->Parts[part];
+ return obj->GetBinarySize();
+}
+
+void vtkWebGLPolyData::GenerateBinaryData()
+{
+ vtkWebGLDataSet* obj;
+ this->hasChanged = false;
+ std::stringstream ss;
+ for (size_t i = 0; i < this->Internal->Parts.size(); i++)
+ {
+ obj = this->Internal->Parts[i];
+ obj->GenerateBinaryData();
+ ss << obj->GetMD5();
+ }
+ if (!this->Internal->Parts.empty())
+ {
+ std::string localMD5;
+ vtkWebGLExporter::ComputeMD5(
+ (const unsigned char*)ss.str().c_str(), static_cast<int>(ss.str().size()), localMD5);
+ this->hasChanged = this->MD5 != localMD5;
+ this->MD5 = localMD5;
+ }
+ else
+ cout << "Warning: GenerateBinaryData() @ vtkWebGLObject: This isn\'t supposed to happen.";
+}
+
+int vtkWebGLPolyData::GetNumberOfParts()
+{
+ return static_cast<int>(this->Internal->Parts.size());
+}
+
+void vtkWebGLPolyData::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+void vtkWebGLPolyData::GetLinesFromPolygon(
+ vtkMapper* mapper, vtkActor* actor, int lineMaxSize, double* edgeColor)
+{
+ vtkWebGLPolyData* object = this;
+ vtkDataSet* dataset = nullptr;
+ vtkSmartPointer<vtkDataSet> tempDS;
+ vtkDataObject* dObj = mapper->GetInputDataObject(0, 0);
+ vtkCompositeDataSet* cd = vtkCompositeDataSet::SafeDownCast(dObj);
+ if (cd)
+ {
+ vtkCompositeDataGeometryFilter* gf = vtkCompositeDataGeometryFilter::New();
+ gf->SetInputData(cd);
+ gf->Update();
+ tempDS = gf->GetOutput();
+ gf->Delete();
+ dataset = tempDS;
+ }
+ else
+ {
+ dataset = mapper->GetInput();
+ }
+
+ int np = 0;
+ int size = 0;
+ for (int i = 0; i < dataset->GetNumberOfCells(); i++)
+ size += dataset->GetCell(i)->GetNumberOfPoints();
+
+ float* points = new float[size * 3];
+ unsigned char* color = new unsigned char[size * 4];
+ int* index = new int[size * 2];
+ double* point;
+ int pos = 0;
+
+ vtkScalarsToColors* table = mapper->GetLookupTable();
+ vtkDataArray* array;
+ if (mapper->GetScalarMode() == VTK_SCALAR_MODE_USE_CELL_FIELD_DATA)
+ {
+ vtkCellData* celldata = dataset->GetCellData();
+ if (actor->GetMapper()->GetArrayAccessMode() == VTK_GET_ARRAY_BY_ID)
+ array = celldata->GetArray(actor->GetMapper()->GetArrayId());
+ else
+ array = celldata->GetArray(actor->GetMapper()->GetArrayName());
+ }
+ else
+ {
+ vtkPointData* pointdata = dataset->GetPointData();
+ if (actor->GetMapper()->GetArrayAccessMode() == VTK_GET_ARRAY_BY_ID)
+ array = pointdata->GetArray(actor->GetMapper()->GetArrayId());
+ else
+ array = pointdata->GetArray(actor->GetMapper()->GetArrayName());
+ }
+
+ int colorComponent = table->GetVectorComponent();
+ int numberOfComponents = 0;
+ if (array != nullptr)
+ numberOfComponents = array->GetNumberOfComponents();
+ int mode = table->GetVectorMode();
+ double mag = 0, rgb[3];
+ int curr = 0;
+ for (int i = 0; i < dataset->GetNumberOfCells(); i++)
+ {
+ vtkCell* cell = dataset->GetCell(i);
+ int b = pos;
+ np = dataset->GetCell(i)->GetNumberOfPoints();
+ for (int j = 0; j < np; j++)
+ {
+ point = cell->GetPoints()->GetPoint(j);
+ points[curr * 3 + j * 3 + 0] = point[0];
+ points[curr * 3 + j * 3 + 1] = point[1];
+ points[curr * 3 + j * 3 + 2] = point[2];
+
+ index[curr * 2 + j * 2 + 0] = pos++;
+ index[curr * 2 + j * 2 + 1] = pos;
+ if (j == np - 1)
+ index[curr * 2 + j * 2 + 1] = b;
+
+ vtkIdType pointId = cell->GetPointIds()->GetId(j);
+ if (numberOfComponents == 0)
+ {
+ actor->GetProperty()->GetColor(rgb);
+ }
+ else
+ {
+ switch (mode)
+ {
+ case vtkScalarsToColors::MAGNITUDE:
+ mag = 0;
+ for (int w = 0; w < numberOfComponents; w++)
+ mag += array->GetComponent(pointId, w) * array->GetComponent(pointId, w);
+ mag = sqrt(mag);
+ table->GetColor(mag, &rgb[0]);
+ break;
+ case vtkScalarsToColors::COMPONENT:
+ mag = array->GetComponent(pointId, colorComponent);
+ table->GetColor(mag, &rgb[0]);
+ break;
+ case vtkScalarsToColors::RGBCOLORS:
+ array->GetTuple(pointId, &rgb[0]);
+ break;
+ }
+ }
+ if (edgeColor != nullptr)
+ memcpy(rgb, edgeColor, sizeof(double) * 3);
+ color[curr * 4 + j * 4 + 0] = (unsigned char)((int)(rgb[0] * 255));
+ color[curr * 4 + j * 4 + 1] = (unsigned char)((int)(rgb[1] * 255));
+ color[curr * 4 + j * 4 + 2] = (unsigned char)((int)(rgb[2] * 255));
+ color[curr * 4 + j * 4 + 3] = (unsigned char)255;
+ }
+ curr += np;
+ }
+ object->SetLine(points, size, index, size * 2, color, lineMaxSize);
+}
+
+void vtkWebGLPolyData::GetLines(vtkTriangleFilter* polydata, vtkActor* actor, int lineMaxSize)
+{
+ vtkWebGLPolyData* object = this;
+ vtkCellArray* lines = polydata->GetOutput(0)->GetLines();
+
+ // Index
+ // Array of 3 Values. [#number of index, i1, i2]
+ // Discarting the first value
+ vtkDataArray* conn = lines->GetConnectivityArray();
+ const vtkIdType connSize = conn->GetNumberOfValues();
+ int* index = new int[static_cast<size_t>(connSize)];
+ for (vtkIdType i = 0; i < connSize; ++i)
+ {
+ index[i] = static_cast<int>(conn->GetComponent(i, 0));
+ }
+ // Point
+ double point[3];
+ float* points = new float[polydata->GetOutput(0)->GetNumberOfPoints() * 3];
+ for (int i = 0; i < polydata->GetOutput(0)->GetNumberOfPoints(); i++)
+ {
+ polydata->GetOutput(0)->GetPoint(i, point);
+ points[i * 3 + 0] = point[0];
+ points[i * 3 + 1] = point[1];
+ points[i * 3 + 2] = point[2];
+ }
+ // Colors
+ unsigned char* color = new unsigned char[polydata->GetOutput(0)->GetNumberOfPoints() * 4];
+ this->GetColorsFromPolyData(color, polydata->GetOutput(0), actor);
+
+ object->SetLine(points, polydata->GetOutput(0)->GetNumberOfPoints(), index,
+ static_cast<int>(connSize), color, lineMaxSize);
+}
+
+void vtkWebGLPolyData::SetPoints(
+ float* points, int numberOfPoints, unsigned char* colors, int maxSize)
+{
+ this->webGlType = wPOINTS;
+
+ // Delete Old Objects
+ vtkWebGLDataSet* obj;
+ while (!this->Internal->Parts.empty())
+ {
+ obj = this->Internal->Parts.back();
+ this->Internal->Parts.pop_back();
+ obj->Delete();
+ }
+
+ // Create new objs
+ int numObjs = (numberOfPoints / maxSize) + 1;
+ int offset = 0;
+ int size = 0;
+ for (int i = 0; i < numObjs; i++)
+ {
+ size = numberOfPoints - offset;
+ if (size > maxSize)
+ size = maxSize;
+
+ float* _points = new float[size * 3];
+ unsigned char* _colors = new unsigned char[size * 4];
+ memcpy(_points, &points[offset * 3], size * 3 * sizeof(float));
+ memcpy(_colors, &colors[offset * 4], size * 4 * sizeof(unsigned char));
+
+ obj = vtkWebGLDataSet::New();
+ obj->SetPoints(_points, size);
+ obj->SetColors(_colors);
+ obj->SetType(wPOINTS);
+ obj->SetMatrix(this->Matrix);
+ this->Internal->Parts.push_back(obj);
+
+ offset += size;
+ }
+
+ delete[] points;
+ delete[] colors;
+}
+
+void vtkWebGLPolyData::GetPoints(vtkTriangleFilter* polydata, vtkActor* actor, int maxSize)
+{
+ vtkWebGLPolyData* object = this;
+
+ // Points
+ double point[3];
+ float* points = new float[polydata->GetOutput(0)->GetNumberOfPoints() * 3];
+ for (int i = 0; i < polydata->GetOutput(0)->GetNumberOfPoints(); i++)
+ {
+ polydata->GetOutput(0)->GetPoint(i, point);
+ points[i * 3 + 0] = point[0];
+ points[i * 3 + 1] = point[1];
+ points[i * 3 + 2] = point[2];
+ }
+ // Colors
+ unsigned char* colors = new unsigned char[polydata->GetOutput(0)->GetNumberOfPoints() * 4];
+ this->GetColorsFromPolyData(colors, polydata->GetOutput(0), actor);
+
+ object->SetPoints(points, polydata->GetOutput(0)->GetNumberOfPoints(), colors, maxSize);
+}
+
+void vtkWebGLPolyData::GetColorsFromPolyData(
+ unsigned char* color, vtkPolyData* polydata, vtkActor* actor)
+{
+ int celldata;
+ vtkDataArray* array = vtkAbstractMapper::GetScalars(polydata, actor->GetMapper()->GetScalarMode(),
+ actor->GetMapper()->GetArrayAccessMode(), actor->GetMapper()->GetArrayId(),
+ actor->GetMapper()->GetArrayName(), celldata);
+ if (actor->GetMapper()->GetScalarVisibility() && array != nullptr)
+ {
+ vtkScalarsToColors* table = actor->GetMapper()->GetLookupTable();
+
+ vtkUnsignedCharArray* cor =
+ table->MapScalars(array, table->GetVectorMode(), table->GetVectorComponent());
+ memcpy(color, cor->GetPointer(0), polydata->GetNumberOfPoints() * 4);
+ cor->Delete();
+ }
+ else
+ {
+ for (int i = 0; i < polydata->GetNumberOfPoints(); i++)
+ {
+ color[i * 4 + 0] = (unsigned char)255;
+ color[i * 4 + 1] = (unsigned char)255;
+ color[i * 4 + 2] = (unsigned char)255;
+ color[i * 4 + 3] = (unsigned char)255;
+ }
+ }
+}
+
+void vtkWebGLPolyData::GetPolygonsFromPointData(
+ vtkTriangleFilter* polydata, vtkActor* actor, int maxSize)
+{
+ vtkWebGLPolyData* object = this;
+
+ vtkPolyDataNormals* polynormals = vtkPolyDataNormals::New();
+ polynormals->SetInputConnection(polydata->GetOutputPort(0));
+ polynormals->Update();
+
+ vtkPolyData* data = polynormals->GetOutput();
+
+ vtkCellArray* poly = data->GetPolys();
+ vtkPointData* point = data->GetPointData();
+ vtkNew<vtkIdTypeArray> ndata;
+ poly->ExportLegacyFormat(ndata);
+ vtkDataSetAttributes* attr = (vtkDataSetAttributes*)point;
+
+ // Vertices
+ float* vertices = new float[data->GetNumberOfPoints() * 3];
+ for (int i = 0; i < data->GetNumberOfPoints() * 3; i++)
+ vertices[i] = data->GetPoint(i / 3)[i % 3];
+ // Index
+ // ndata contain 4 values for the normal: [number of values per index, index[3]]
+ // We don't need the first value
+ int* indexes = new int[ndata->GetSize() * 3 / 4];
+ for (int i = 0; i < ndata->GetSize(); i++)
+ if (i % 4 != 0)
+ indexes[i * 3 / 4] = ndata->GetValue(i);
+ // Normal
+ float* normal = new float[attr->GetNormals()->GetSize()];
+ for (int i = 0; i < attr->GetNormals()->GetSize(); i++)
+ normal[i] = attr->GetNormals()->GetComponent(0, i);
+ // Colors
+ unsigned char* color = new unsigned char[data->GetNumberOfPoints() * 4];
+ this->GetColorsFromPointData(color, point, data, actor);
+ // TCoord
+ float* tcoord = nullptr;
+ if (attr->GetTCoords())
+ {
+ tcoord = new float[attr->GetTCoords()->GetSize()];
+ for (int i = 0; i < attr->GetTCoords()->GetSize(); i++)
+ tcoord[i] = attr->GetTCoords()->GetComponent(0, i);
+ }
+
+ object->SetMesh(vertices, data->GetNumberOfPoints(), indexes, ndata->GetSize() * 3 / 4, normal,
+ color, tcoord, maxSize);
+ polynormals->Delete();
+}
+
+void vtkWebGLPolyData::GetPolygonsFromCellData(
+ vtkTriangleFilter* polydata, vtkActor* actor, int maxSize)
+{
+ vtkWebGLPolyData* object = this;
+
+ vtkPolyDataNormals* polynormals = vtkPolyDataNormals::New();
+ polynormals->SetInputConnection(polydata->GetOutputPort(0));
+ polynormals->Update();
+
+ vtkPolyData* data = polynormals->GetOutput();
+ vtkCellData* celldata = data->GetCellData();
+
+ vtkDataArray* array;
+ if (actor->GetMapper()->GetArrayAccessMode() == VTK_GET_ARRAY_BY_ID)
+ array = celldata->GetArray(actor->GetMapper()->GetArrayId());
+ else
+ array = celldata->GetArray(actor->GetMapper()->GetArrayName());
+ vtkScalarsToColors* table = actor->GetMapper()->GetLookupTable();
+ int colorComponent = table->GetVectorComponent();
+ int mode = table->GetVectorMode();
+
+ float* vertices = new float[data->GetNumberOfCells() * 3 * 3];
+ float* normals = new float[data->GetNumberOfCells() * 3 * 3];
+ unsigned char* colors = new unsigned char[data->GetNumberOfCells() * 3 * 4];
+ int* indexes = new int[data->GetNumberOfCells() * 3 * 3];
+
+ vtkGenericCell* cell = vtkGenericCell::New();
+ double tuple[3], normal[3], color[3];
+ color[0] = 1.0;
+ color[1] = 1.0;
+ color[2] = 1.0;
+ vtkPoints* points;
+ int aux;
+ double mag, alpha = 1.0;
+ int numberOfComponents = 0;
+ if (array)
+ numberOfComponents = array->GetNumberOfComponents();
+ else
+ mode = -1;
+ for (int i = 0; i < data->GetNumberOfCells(); i++)
+ {
+ data->GetCell(i, cell);
+ points = cell->GetPoints();
+
+ // getColors
+ alpha = 1.0;
+ switch (mode)
+ {
+ case -1:
+ actor->GetProperty()->GetColor(color);
+ alpha = actor->GetProperty()->GetOpacity();
+ break;
+ case vtkScalarsToColors::MAGNITUDE:
+ mag = 0;
+ for (int w = 0; w < numberOfComponents; w++)
+ mag += array->GetComponent(i, w) * array->GetComponent(i, w);
+ mag = sqrt(mag);
+ table->GetColor(mag, &color[0]);
+ alpha = table->GetOpacity(mag);
+ break;
+ case vtkScalarsToColors::COMPONENT:
+ mag = array->GetComponent(i, colorComponent);
+ table->GetColor(mag, &color[0]);
+ alpha = table->GetOpacity(mag);
+ break;
+ case vtkScalarsToColors::RGBCOLORS:
+ array->GetTuple(i, &color[0]);
+ break;
+ }
+ // getNormals
+ celldata->GetNormals()->GetTuple(i, &normal[0]);
+ for (int j = 0; j < 3; j++)
+ {
+ aux = i * 9 + j * 3;
+ // Normals
+ normals[aux + 0] = normal[0];
+ normals[aux + 1] = normal[1];
+ normals[aux + 2] = normal[2];
+ // getVertices
+ points->GetPoint(j, &tuple[0]);
+ vertices[aux + 0] = tuple[0];
+ vertices[aux + 1] = tuple[1];
+ vertices[aux + 2] = tuple[2];
+ // Colors
+ colors[4 * (3 * i + j) + 0] = (unsigned char)((int)(color[0] * 255));
+ colors[4 * (3 * i + j) + 1] = (unsigned char)((int)(color[1] * 255));
+ colors[4 * (3 * i + j) + 2] = (unsigned char)((int)(color[2] * 255));
+ colors[4 * (3 * i + j) + 3] = (unsigned char)((int)(alpha * 255));
+ // getIndexes
+ indexes[aux + 0] = aux + 0;
+ indexes[aux + 1] = aux + 1;
+ indexes[aux + 2] = aux + 2;
+ }
+ }
+ object->SetMesh(vertices, data->GetNumberOfCells() * 3, indexes, data->GetNumberOfCells() * 3,
+ normals, colors, nullptr, maxSize);
+ cell->Delete();
+ polynormals->Delete();
+}
+
+void vtkWebGLPolyData::GetColorsFromPointData(
+ unsigned char* color, vtkPointData* pointdata, vtkPolyData* polydata, vtkActor* actor)
+{
+ vtkDataSetAttributes* attr = (vtkDataSetAttributes*)pointdata;
+
+ int colorSize = attr->GetNormals()->GetSize() * 4 / 3;
+
+ vtkDataArray* array;
+ if (actor->GetMapper()->GetArrayAccessMode() == VTK_GET_ARRAY_BY_ID)
+ array = pointdata->GetArray(actor->GetMapper()->GetArrayId());
+ else
+ array = pointdata->GetArray(actor->GetMapper()->GetArrayName());
+
+ if (array && actor->GetMapper()->GetScalarVisibility() &&
+ actor->GetMapper()->GetArrayName() != nullptr && actor->GetMapper()->GetArrayName()[0] != '\0')
+ {
+ vtkScalarsToColors* table = actor->GetMapper()->GetLookupTable();
+ int colorComponent = table->GetVectorComponent(),
+ numberOfComponents = array->GetNumberOfComponents();
+ int mode = table->GetVectorMode();
+ double mag = 0, rgb[3];
+ double alpha = 1.0;
+
+ if (numberOfComponents == 1 && mode == vtkScalarsToColors::MAGNITUDE)
+ {
+ mode = vtkScalarsToColors::COMPONENT;
+ colorComponent = 0;
+ }
+ for (int i = 0; i < colorSize / 4; i++)
+ {
+ switch (mode)
+ {
+ case vtkScalarsToColors::MAGNITUDE:
+ mag = 0;
+ for (int w = 0; w < numberOfComponents; w++)
+ mag += array->GetComponent(i, w) * array->GetComponent(i, w);
+ mag = sqrt(mag);
+ table->GetColor(mag, &rgb[0]);
+ alpha = table->GetOpacity(mag);
+ break;
+ case vtkScalarsToColors::COMPONENT:
+ mag = array->GetComponent(i, colorComponent);
+ table->GetColor(mag, &rgb[0]);
+ alpha = table->GetOpacity(mag);
+ break;
+ case vtkScalarsToColors::RGBCOLORS:
+ array->GetTuple(i, &rgb[0]);
+ alpha = actor->GetProperty()->GetOpacity();
+ break;
+ }
+ color[i * 4 + 0] = (unsigned char)((int)(rgb[0] * 255));
+ color[i * 4 + 1] = (unsigned char)((int)(rgb[1] * 255));
+ color[i * 4 + 2] = (unsigned char)((int)(rgb[2] * 255));
+ color[i * 4 + 3] = (unsigned char)((int)(alpha * 255));
+ }
+ }
+ else
+ {
+ double rgb[3];
+ double alpha = 0;
+ int celldata;
+ array = vtkAbstractMapper::GetScalars(polydata, actor->GetMapper()->GetScalarMode(),
+ actor->GetMapper()->GetArrayAccessMode(), actor->GetMapper()->GetArrayId(),
+ actor->GetMapper()->GetArrayName(), celldata);
+ if (actor->GetMapper()->GetScalarVisibility() &&
+ (actor->GetMapper()->GetColorMode() == VTK_COLOR_MODE_DEFAULT ||
+ actor->GetMapper()->GetColorMode() == VTK_COLOR_MODE_DIRECT_SCALARS) &&
+ array)
+ {
+ vtkScalarsToColors* table = actor->GetMapper()->GetLookupTable();
+ vtkUnsignedCharArray* cor =
+ table->MapScalars(array, actor->GetMapper()->GetColorMode(), table->GetVectorComponent());
+ memcpy(color, cor->GetPointer(0), polydata->GetNumberOfPoints() * 4);
+ cor->Delete();
+ }
+ else
+ {
+ actor->GetProperty()->GetColor(rgb);
+ alpha = actor->GetProperty()->GetOpacity();
+ for (int i = 0; i < colorSize / 4; i++)
+ {
+ color[i * 4 + 0] = (unsigned char)((int)(rgb[0] * 255));
+ color[i * 4 + 1] = (unsigned char)((int)(rgb[1] * 255));
+ color[i * 4 + 2] = (unsigned char)((int)(rgb[2] * 255));
+ color[i * 4 + 3] = (unsigned char)((int)(alpha * 255));
+ }
+ }
+ }
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebGLPolyData
+ * @brief PolyData representation for WebGL.
+ */
+
+#ifndef vtkWebGLPolyData_h
+#define vtkWebGLPolyData_h
+
+#include "vtkWebGLExporterModule.h" // needed for export macro
+#include "vtkWebGLObject.h"
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkActor;
+class vtkMatrix4x4;
+class vtkMapper;
+class vtkPointData;
+class vtkPolyData;
+class vtkTriangleFilter;
+
+class VTKWEBGLEXPORTER_EXPORT vtkWebGLPolyData : public vtkWebGLObject
+{
+public:
+ static vtkWebGLPolyData* New();
+ vtkTypeMacro(vtkWebGLPolyData, vtkWebGLObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ void GenerateBinaryData() override;
+ unsigned char* GetBinaryData(int part) override;
+ int GetBinarySize(int part) override;
+ int GetNumberOfParts() override;
+
+ void GetPoints(vtkTriangleFilter* polydata, vtkActor* actor, int maxSize);
+
+ void GetLinesFromPolygon(vtkMapper* mapper, vtkActor* actor, int lineMaxSize, double* edgeColor);
+ void GetLines(vtkTriangleFilter* polydata, vtkActor* actor, int lineMaxSize);
+ void GetColorsFromPolyData(unsigned char* color, vtkPolyData* polydata, vtkActor* actor);
+
+ // Get following data from the actor
+ void GetPolygonsFromPointData(vtkTriangleFilter* polydata, vtkActor* actor, int maxSize);
+ void GetPolygonsFromCellData(vtkTriangleFilter* polydata, vtkActor* actor, int maxSize);
+ void GetColorsFromPointData(
+ unsigned char* color, vtkPointData* pointdata, vtkPolyData* polydata, vtkActor* actor);
+
+ void SetMesh(float* _vertices, int _numberOfVertices, int* _index, int _numberOfIndexes,
+ float* _normals, unsigned char* _colors, float* _tcoords, int maxSize);
+ void SetLine(float* _points, int _numberOfPoints, int* _index, int _numberOfIndex,
+ unsigned char* _colors, int maxSize);
+ void SetPoints(float* points, int numberOfPoints, unsigned char* colors, int maxSize);
+ void SetTransformationMatrix(vtkMatrix4x4* m);
+
+protected:
+ vtkWebGLPolyData();
+ ~vtkWebGLPolyData() override;
+
+private:
+ vtkWebGLPolyData(const vtkWebGLPolyData&) = delete;
+ void operator=(const vtkWebGLPolyData&) = delete;
+
+ class vtkInternal;
+ vtkInternal* Internal;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "vtkWebGLWidget.h"
+
+#include "vtkActor2D.h"
+#include "vtkDiscretizableColorTransferFunction.h"
+#include "vtkObjectFactory.h"
+#include "vtkScalarBarActor.h"
+#include "vtkWebGLExporter.h"
+#include "vtkWebGLObject.h"
+
+#include <sstream>
+
+VTK_ABI_NAMESPACE_BEGIN
+vtkStandardNewMacro(vtkWebGLWidget);
+
+vtkWebGLWidget::vtkWebGLWidget()
+{
+ this->binaryData = nullptr;
+ this->iswidget = false;
+ this->binarySize = 0;
+ this->orientation = 1;
+ this->interactAtServer = false;
+ this->title = nullptr;
+}
+
+vtkWebGLWidget::~vtkWebGLWidget()
+{
+ delete[] this->binaryData;
+ while (!this->colors.empty())
+ {
+ double* xrgb = this->colors.back();
+ this->colors.pop_back();
+ delete[] xrgb;
+ }
+ delete[] this->title;
+}
+
+unsigned char* vtkWebGLWidget::GetBinaryData(int vtkNotUsed(part))
+{
+ this->hasChanged = false;
+ return this->binaryData;
+}
+
+int vtkWebGLWidget::GetBinarySize(int vtkNotUsed(part))
+{
+ return this->binarySize;
+}
+
+void vtkWebGLWidget::GenerateBinaryData()
+{
+ delete[] this->binaryData;
+ std::string oldMD5 = this->MD5;
+
+ size_t pos = 0;
+ // Calculate the size used
+ // NumOfColors, Type, Position, Size, Colors, Orientation, numberOfLabels
+ int total = static_cast<int>(sizeof(int) + 1 + 4 * sizeof(float) +
+ this->colors.size() * (sizeof(float) + 3 * sizeof(char)) + 1 + 1 + strlen(this->title));
+ this->binaryData = new unsigned char[total];
+ int colorSize = static_cast<int>(this->colors.size());
+
+ memset(this->binaryData, 0, total); // Fill array with 0
+ memcpy(&this->binaryData[pos], &colorSize, sizeof(int));
+ pos += sizeof(int); // Binary Data Size
+ this->binaryData[pos++] = 'C'; // Object Type
+ memcpy(&this->binaryData[pos], &this->position, sizeof(float) * 2);
+ pos += sizeof(float) * 2; // Position (double[2])
+ memcpy(&this->binaryData[pos], &this->size, sizeof(float) * 2);
+ pos += sizeof(float) * 2; // Size (double[2])
+ unsigned char rgb[3];
+ for (size_t i = 0; i < colors.size(); i++) // Array of Colors (double, char[3])
+ {
+ float v = (float)this->colors[i][0];
+ memcpy(&this->binaryData[pos], &v, sizeof(float));
+ pos += sizeof(float);
+ rgb[0] = (unsigned char)((int)(this->colors[i][1] * 255));
+ rgb[1] = (unsigned char)((int)(this->colors[i][2] * 255));
+ rgb[2] = (unsigned char)((int)(this->colors[i][3] * 255));
+ memcpy(&this->binaryData[pos], rgb, 3 * sizeof(unsigned char));
+ pos += sizeof(unsigned char) * 3;
+ }
+ unsigned char aux;
+ aux = (unsigned char)this->orientation;
+ memcpy(&this->binaryData[pos], &aux, 1);
+ pos++;
+ aux = (unsigned char)this->numberOfLabels;
+ memcpy(&this->binaryData[pos], &aux, 1);
+ pos++;
+ memcpy(&this->binaryData[pos], this->title, strlen(this->title));
+ pos += strlen(this->title);
+
+ this->binarySize = total;
+ vtkWebGLExporter::ComputeMD5(this->binaryData, total, this->MD5);
+ this->hasChanged = this->MD5 != oldMD5;
+}
+
+int vtkWebGLWidget::GetNumberOfParts()
+{
+ return 1;
+}
+
+void vtkWebGLWidget::PrintSelf(ostream& os, vtkIndent indent)
+{
+ this->Superclass::PrintSelf(os, indent);
+}
+
+void vtkWebGLWidget::GetDataFromColorMap(vtkActor2D* actor)
+{
+ vtkScalarBarActor* scalarbar = vtkScalarBarActor::SafeDownCast(actor);
+ this->numberOfLabels = scalarbar->GetNumberOfLabels();
+
+ std::stringstream theTitle;
+ char* componentTitle = scalarbar->GetComponentTitle();
+
+ theTitle << scalarbar->GetTitle();
+ if (componentTitle && strlen(componentTitle) > 0)
+ {
+ theTitle << " ";
+ theTitle << componentTitle;
+ }
+
+ delete[] this->title;
+ std::string tmp = theTitle.str();
+ this->title = new char[tmp.length() + 1];
+ strcpy(this->title, tmp.c_str());
+ this->hasTransparency = (scalarbar->GetUseOpacity() != 0);
+ this->orientation = scalarbar->GetOrientation();
+
+ // Colors
+ vtkDiscretizableColorTransferFunction* lookup =
+ vtkDiscretizableColorTransferFunction::SafeDownCast(scalarbar->GetLookupTable());
+ int num = 5 * lookup->GetSize();
+ double* range = lookup->GetRange();
+ double v, s;
+ v = range[0];
+ s = (range[1] - range[0]) / (num - 1);
+ for (int i = 0; i < num; i++)
+ {
+ double* xrgb = new double[4];
+ scalarbar->GetLookupTable()->GetColor(v, &xrgb[1]);
+ xrgb[0] = v;
+ this->colors.push_back(xrgb);
+ v += s;
+ }
+
+ this->textFormat = scalarbar->GetLabelFormat(); // Float Format ex.: %-#6.3g
+ this->textPosition = scalarbar->GetTextPosition(); // Orientacao dos textos; 1;
+ double* thePos = scalarbar->GetPosition();
+ double* theSize = scalarbar->GetPosition2();
+ this->position[0] = thePos[0];
+ this->position[1] = thePos[1]; // Widget Position
+ this->size[0] = theSize[0];
+ this->size[1] = theSize[1]; // Widget Size
+}
+VTK_ABI_NAMESPACE_END
--- /dev/null
+// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @class vtkWebGLWidget
+ * @brief Widget representation for WebGL.
+ */
+
+#ifndef vtkWebGLWidget_h
+#define vtkWebGLWidget_h
+
+#include "vtkWebGLExporterModule.h" // needed for export macro
+#include "vtkWebGLObject.h"
+
+#include <vector> // Needed to store colors
+
+VTK_ABI_NAMESPACE_BEGIN
+class vtkActor2D;
+
+class VTKWEBGLEXPORTER_EXPORT vtkWebGLWidget : public vtkWebGLObject
+{
+public:
+ static vtkWebGLWidget* New();
+ vtkTypeMacro(vtkWebGLWidget, vtkWebGLObject);
+ void PrintSelf(ostream& os, vtkIndent indent) override;
+
+ void GenerateBinaryData() override;
+ unsigned char* GetBinaryData(int part) override;
+ int GetBinarySize(int part) override;
+ int GetNumberOfParts() override;
+
+ void GetDataFromColorMap(vtkActor2D* actor);
+
+protected:
+ vtkWebGLWidget();
+ ~vtkWebGLWidget() override;
+
+ unsigned char* binaryData;
+ int binarySize;
+ int orientation;
+ char* title;
+ char* textFormat;
+ int textPosition;
+ float position[2];
+ float size[2];
+ int numberOfLabels;
+ std::vector<double*> colors; // x, r, g, b
+
+private:
+ vtkWebGLWidget(const vtkWebGLWidget&) = delete;
+ void operator=(const vtkWebGLWidget&) = delete;
+};
+
+VTK_ABI_NAMESPACE_END
+#endif
--- /dev/null
+/**
+ * Create a renderer object working fully in WebGL
+ * Here is a sample set of command to illustrate how to use this renderer
+ *
+ * var renderer = new WebGLRenderer('rendererId','http://localhost:8080/ParaViewWebService')
+ * renderer.init(sessionId, viewId);
+ * renderer.bindToElementId('containerID'); // => Add a WebGL canvas inside a div tag id 'containerID'
+ * renderer.start();
+ *
+ * renderer.init(otherSessionId, otherViewId);
+ * renderer.view.width = '100';
+ * renderer.view.height = '400';
+ * renderer.setSize('100', '400');
+ *
+ * renderer.unbindToElementId('containerID');
+ */
+
+// Global object to keep track of WebGL renderers
+var webglRenderers = new Object();
+
+window.requestAnimFrame = (function(){
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(/* function */ callback, /* DOMElement */ element){
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function WebGLRenderer(rendererId, coreServiceURL) {
+ this.baseURL = coreServiceURL + "/WebGL";
+ this.rendererId = rendererId;
+ this.sessionId = "";
+ this.viewId = "";
+ this.nbError = 0;
+ this.localTimeStamp = 0;
+ this.offlineMode = false;
+ this.setServerMode(false);
+ this.forceSquareSize = false;
+
+ this.view = new Object();
+ this.view.width = 100;
+ this.view.height = 100;
+ this.view.id = rendererId;
+ this.view.alt = "ParaView Renderer";
+
+ //Default Shaders
+ this.view.shaderfs = document.createElement("script");
+ this.view.shaderfs.id = "shader-fs";
+ this.view.shaderfs.type = "x-shader/x-fragment";
+ this.view.shaderfs.innerHTML = "\
+ #ifdef GL_ES\n\
+ precision highp float;\n\
+ #endif\n\
+ uniform bool uIsLine;\
+ varying vec4 vColor;\
+ varying vec4 vTransformedNormal;\
+ varying vec4 vPosition;\
+ void main(void) {\
+ float directionalLightWeighting1 = max(dot(normalize(vTransformedNormal.xyz), vec3(0.0, 0.0, 1.0)), 0.0); \
+ float directionalLightWeighting2 = max(dot(normalize(vTransformedNormal.xyz), vec3(0.0, 0.0, -1.0)), 0.0);\
+ vec3 lightWeighting = max(vec3(1.0, 1.0, 1.0) * directionalLightWeighting1, vec3(1.0, 1.0, 1.0) * directionalLightWeighting2);\
+ if (uIsLine == false){\
+ gl_FragColor = vec4(vColor.rgb * lightWeighting, vColor.a);\
+ } else {\
+ gl_FragColor = vColor*vec4(1.0, 1.0, 1.0, 1.0);\
+ }\
+ }";
+ this.view.shadervs = document.createElement("script");
+ this.view.shadervs.id = "shader-vs";
+ this.view.shadervs.type = "x-shader/x-vertex";
+ this.view.shadervs.innerHTML = "\
+ attribute vec3 aVertexPosition;\
+ attribute vec4 aVertexColor;\
+ attribute vec3 aVertexNormal;\
+ uniform mat4 uMVMatrix;\
+ uniform mat4 uPMatrix;\
+ uniform mat4 uNMatrix;\
+ varying vec4 vColor;\
+ varying vec4 vPosition;\
+ varying vec4 vTransformedNormal;\
+ void main(void) {\
+ vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);\
+ gl_Position = uPMatrix * vPosition;\
+ vTransformedNormal = uNMatrix * vec4(aVertexNormal, 1.0);\
+ vColor = aVertexColor;\
+ }";
+
+ // Point Shaders
+ this.view.shaderfsPoint = document.createElement("script");
+ this.view.shaderfsPoint.id = "shader-fs-Point";
+ this.view.shaderfsPoint.type = "x-shader/x-fragment";
+ this.view.shaderfsPoint.innerHTML = "\
+ #ifdef GL_ES\n\
+ precision highp float;\n\
+ #endif\n\
+ varying vec4 vColor;\
+ void main(void) {\
+ gl_FragColor = vColor;\
+ }";
+ this.view.shadervsPoint = document.createElement("script");
+ this.view.shadervsPoint.id = "shader-vs-Point";
+ this.view.shadervsPoint.type = "x-shader/x-vertex";
+ this.view.shadervsPoint.innerHTML = "\
+ attribute vec3 aVertexPosition;\
+ attribute vec4 aVertexColor;\
+ uniform mat4 uMVMatrix;\
+ uniform mat4 uPMatrix;\
+ uniform mat4 uNMatrix;\
+ uniform float uPointSize;\
+ varying vec4 vColor;\
+ void main(void) {\
+ vec4 pos = uMVMatrix * vec4(aVertexPosition, 1.0);\
+ gl_Position = uPMatrix * pos;\
+ vColor = aVertexColor*vec4(1.0, 1.0, 1.0, 1.0);\
+ gl_PointSize = uPointSize;\
+ }";
+
+ //
+ this.canvasName = "glcanvas" + rendererId;
+ this.view.html = '<div><canvas id="' + this.canvasName + '" style="border: none; overflow: hidden;';
+ if (this.forceSquareSize == true) this.view.html += ' position: absolute;';
+ this.view.html += ' left:-1px; top:-1px; right:0px; z-index=0;" width="' + this.view.width
+ + '" height="' + this.view.height + '"onmousedown="handleMouseDown(event,\''+rendererId+'\')"\
+ onmousemove="handleMouseMove(event,\''+rendererId+'\')" onmouseup="handleMouseUp(event,\'' + rendererId
+ + '\')" oncontextmenu="consumeEvent(event)"> Your browser doesn\'t appear to support the HTML5 <code>\
+ <canvas></code> element.</canvas>';
+
+ this.view.html += '<canvas id="' + this.canvasName + 'Widget" style="position: absolute; left:-1px; top:-1px; z-index:1;';
+ if(this.forceSquareSize == true) this.view.html += 'position: absolute;';
+ this.view.html += '" width="' + this.view.width + '" height="' + this.view.height +
+ '"onmousedown="handleMouseDown(event,\''+rendererId+'\')" onmousemove="handleMouseMove(event,\''+rendererId+'\')"\
+ onmouseup="handleMouseUp(event,\'' + rendererId + '\')" oncontextmenu="consumeEvent(event)"></canvas></div>';
+ this.fps = 0;
+
+ // Register in global var
+ webglRenderers[rendererId] = this;
+}
+
+WebGLRenderer.prototype.bindToElementId = function (elementId) {
+ this.oldInnerHTML = document.getElementById(elementId).innerHTML;
+ document.getElementById(elementId).innerHTML = this.view.html;
+
+ document.getElementById(elementId).appendChild(this.view.shaderfs);
+ document.getElementById(elementId).appendChild(this.view.shadervs);
+ document.getElementById(elementId).appendChild(this.view.shaderfsPoint);
+ document.getElementById(elementId).appendChild(this.view.shadervsPoint);
+}
+
+WebGLRenderer.prototype.unbindToElementId = function (elementId) {
+ document.getElementById(elementId).innerHTML = this.oldInnerHTML;
+ clearTimeout(this.drawInterval);
+ if (typeof(paraview) != "undefined") paraview.updateConfiguration(true, "JPEG", "NO");
+}
+
+WebGLRenderer.prototype.setOfflineMode = function (mode) {
+ this.offlineMode = mode;
+ this.requestMetaData();
+}
+
+WebGLRenderer.prototype.bindToElement = function (element) {
+ this.oldInnerHTML = element.innerHTML;
+ element.innerHTML = this.view.html;
+
+ element.appendChild(this.view.shaderfs);
+ element.appendChild(this.view.shadervs);
+ element.appendChild(this.view.shaderfsPoint);
+ element.appendChild(this.view.shadervsPoint);
+}
+
+WebGLRenderer.prototype.unbindToElement = function (element) {
+ element.innerHTML = this.oldInnerHTML;
+ clearTimeout(this.drawInterval);
+ if (typeof(paraview) != "undefined") paraview.updateConfiguration(true, "JPEG", "NO");
+}
+
+WebGLRenderer.prototype.init = function (sessionId, viewId) {
+ this.sessionId = sessionId;
+ this.viewId = viewId;
+}
+
+WebGLRenderer.prototype.start = function(metadata, objects) {
+ if (typeof(renderers) == "undefined"){
+ renderers = Object();
+ renderers.current = this;
+ }
+ if (typeof(paraview) != "undefined") paraview.updateConfiguration(true, "JPEG", "WebGL");
+ canvas = document.getElementById(this.canvasName);
+ canvas.width = this.view.width;
+ canvas.height = this.view.height;
+
+ this.hasSceneChanged = true; //Scene Graph Has Changed
+ this.oldCamPos = null; //Last Known Camera Position
+ this.sceneJSON = null; //Current Scene Graph
+ this.up = [];
+ this.right = [];
+ this.z_dir = [];
+ this.objects = []; //List of objects
+ this.nbErrors = 0; //Number of Errors
+ this.background = null; //Background object: mesh, normals, colors, render
+ this.interactionRatio = 2;
+ this.requestInterval = 250; //Frequency it request new data from the server
+ this.requestOldInterval = 250; //
+ this.updateInterval = 100; //Frequency the server will be updated
+ this.fps = 0;
+ this.frames = 0;
+ this.lastTime = new Date().getTime();
+ this.view.aspectRatio = 1;
+ this.lookAt = [0,0,0,0,1,0,0,0,1];
+ this.offlineMode = !(typeof(metadata)=="undefined" || typeof(objects)=="undefined");
+
+ this.cachedObjects = []; //List of Cached Objects
+ this.isCaching = false; //Is Caching or Not
+
+ this.processQueue = []; //List of process to be executed
+
+ this.objScale = 1.0; //Scale applied locally in the scene
+ this.translation = [0.0, 0.0, 0.0]; //Translation
+ this.rotMatrix = mat4.create(); //Rotation Matrix
+ mat4.identity(this.rotMatrix);
+ this.rotMatrix2 = mat4.create(this.rotMatrix);
+
+ this.mouseDown = false;
+ this.lastMouseX = 0;
+ this.lastMouseY = 0;
+
+ this.mvMatrix = mat4.create(this.rotMatrix);
+ this.pMatrix = mat4.create(this.rotMatrix);
+
+ // Initialize the GL context
+ this.gl = null;
+ try {
+ this.gl = canvas.getContext("experimental-webgl");
+ this.gl.viewportWidth = this.view.width;
+ this.gl.viewportHeight = this.view.height;
+ } catch(e) {}
+
+ if (this.gl) {
+ this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ this.gl.clearDepth(1.0);
+ this.gl.enable(this.gl.DEPTH_TEST);
+ this.gl.depthFunc(this.gl.LEQUAL);
+
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
+
+ this.initShaders();
+
+ this.ctx2d = document.getElementById(this.canvasName + "Widget").getContext('2d');
+ // Set up to draw the scene periodically.
+ this.drawInterval = requestAnimFrame(new Function("webglRenderers['" + this.view.id + "'].drawScene();"));
+
+ if (!this.offlineMode){
+ this.requestMetaData();
+ this.updateCamera();
+ } else {
+ this.sceneJSON = JSON.parse(metadata);
+
+ for(aw=0; aw<objects.length-1; aw++){
+ obj = new Object();
+ obj.data = objects[aw];
+ obj.hasTransparency = this.sceneJSON.Objects[aw].transparency;
+ obj.layer = this.sceneJSON.Objects[aw].layer;
+ obj.render = function(){};
+ this.processQueue[aw] = obj;
+ this.objects[this.objects.length] = obj;
+ }
+ }
+ } else {
+ canvas.parentNode.innerHTML = "<table width=100% height=100%><tr><td align=center>\
+ Sorry, your browser do not support WebGL.<br> For more information visit the website\
+ <a href='http://get.webgl.org/' target='_blank'>http://get.webgl.org/</a></td></tr></table>";
+ }
+}
+
+WebGLRenderer.prototype.setForceSquareSize = function(b){
+this.forceSquareSize = b;
+}
+
+WebGLRenderer.prototype.getPageX = function(){
+ var location = 0;
+ var node = document.getElementById(this.canvasName);
+ while(node) {
+ location += node.offsetLeft;
+ node = node.offsetParent;
+ }
+ return location;
+}
+
+WebGLRenderer.prototype.getPageY = function(){
+ var location = 0;
+ var node = document.getElementById(this.canvasName);
+ while(node) {
+ location += node.offsetTop;
+ node = node.offsetParent;
+ }
+ return location;
+}
+
+WebGLRenderer.prototype.setServerMode = function(mode){
+ if (typeof(this.interaction) == "undefined"){
+ this.interaction = new Object();
+ this.interaction.lastRealEvent = 0;
+ this.interaction.needUp = false;
+ this.interaction.lastEvent = 0;
+ this.interaction.isDragging = false;
+ this.interaction.action = " ";
+ this.interaction.keys = " ";
+ this.interaction.button = 0;
+ this.interaction.x = 0;
+ this.interaction.y = 0;
+ this.interaction.xOrigin = 0;
+ this.interaction.yOrigin = 0;
+ this.serverMode = false;
+ }
+ if (this.serverMode == mode) return;
+ this.serverMode = mode;
+ if (!this.serverMode){
+ this.updateId = setTimeout("webglRenderers[\'" + this.view.id + "\'].updateCamera()", this.updateInterval);
+ }
+ canvas = document.getElementById(this.canvasName);
+ canvasWidget = document.getElementById(this.canvasName + "Widget");
+ if (this.serverMode){
+ this.requestOldInterval = this.requestInterval;
+ this.requestInterval = 50;
+ canvas.setAttribute("onmousedown", "mouseServerInt('"+this.view.id+"','"+this.sessionId+"','"+this.viewId+"','down',event)");
+ canvas.setAttribute("onmousemove", "mouseServerInt('"+this.view.id+"','"+this.sessionId+"','"+this.viewId+"','move',event)");
+ canvas.setAttribute("onmouseup" , "mouseServerInt('"+this.view.id+"','"+this.sessionId+"','"+this.viewId+"','up',event)");
+ canvasWidget.setAttribute("onmousedown", "mouseServerInt('"+this.view.id+"','"+this.sessionId+"','"+this.viewId+"','down',event)");
+ canvasWidget.setAttribute("onmousemove", "mouseServerInt('"+this.view.id+"','"+this.sessionId+"','"+this.viewId+"','move',event)");
+ canvasWidget.setAttribute("onmouseup" , "mouseServerInt('"+this.view.id+"','"+this.sessionId+"','"+this.viewId+"','up',event)");
+ } else {
+ this.requestInterval = this.requestOldInterval;
+ canvas.setAttribute("onmousedown", "handleMouseDown(event,'" + this.rendererId + "')");
+ canvas.setAttribute("onmousemove", "handleMouseMove(event,'" + this.rendererId + "')");
+ canvas.setAttribute("onmouseup" , "handleMouseUp(event,'" + this.rendererId + "')");
+ canvasWidget.setAttribute("onmousedown", "handleMouseDown(event,'" + this.rendererId + "')");
+ canvasWidget.setAttribute("onmousemove", "handleMouseMove(event,'" + this.rendererId + "')");
+ canvasWidget.setAttribute("onmouseup" , "handleMouseUp(event,'" + this.rendererId + "')");
+ }
+ canvas.setAttribute("oncontextmenu", "consumeEvent(event)");
+}
+
+WebGLRenderer.prototype.setSize = function(width, height) {
+ width = parseFloat(width);
+ height = parseFloat(height);
+ w = width;
+ h = height;
+ this.view.aspectRatio = width/height;
+ if(this.forceSquareSize){
+ if (width > height) height = width;
+ else width = height;
+ }
+ this.view.width = width;
+ this.view.height = height;
+ canvas = document.getElementById(this.canvasName);
+ canvasWidget = document.getElementById(this.canvasName + "Widget");
+ if (canvas){
+ canvas.width = this.view.width;
+ canvas.height = this.view.height;
+ canvasWidget.width = this.view.width;
+ canvasWidget.height = this.view.height;
+ if (typeof(this.gl) != "undefined" && this.gl != null){
+ if (!this.offlineMode) updateRendererSize(this.sessionId, this.viewId, width, height);
+ this.gl.viewportWidth = this.view.width;
+ this.gl.viewportHeight = this.view.height;
+ }
+ left = 0; tt = 0;
+ if (this.forceSquareSize){
+ left = Math.round((w-this.view.width)/2);
+ tt = Math.round((h-this.view.height)/2);
+ }
+ this.view.left = left;
+ this.view.top = top;
+ if(this.forceSquareSize == true){
+ canvas.setAttribute("style", "position: absolute; overflow: hidden; left: " + left + "px; top: " + tt + "px; right: 0px; z-index:0;");
+ canvasWidget.setAttribute("style", "position: absolute; overflow: hidden; left: " + left + "px; top: " + tt + "px; right: 0px; z-index:1;");
+ } else {
+ canvas.setAttribute("style", "overflow: hidden; left: " + left + "px; top: " + tt + "px; right: 0px; z-index:0;");
+ canvasWidget.setAttribute("style", "position: absolute; overflow: hidden; left: " + left + "px; top: " + tt + "px; right: 0px; z-index:1;");
+ }
+ }
+}
+
+WebGLRenderer.prototype.requestMetaData = function() {
+ if (this.mouseDown || renderers.current != this) return;
+ if (this.offlineMode) return;
+
+ interval = this.requestInterval;
+ if (this.serverMode) interval = interval/2;
+ this.timer = setTimeout("webglRenderers[\'" + this.view.id + "\'].requestMetaData()", interval);
+ var request = new XMLHttpRequest();
+ request.requester = this;
+ filename = this.baseURL + "?sid=" + this.sessionId + "&vid=" + this.viewId + "&q=meta";
+ try {
+ request.open("GET", filename, false);
+ request.overrideMimeType('text/plain; charset=x-user-defined');
+ request.onreadystatechange = function() {
+ if(this.requester.mouseDown) return;
+ if (request.status != 200) this.requester.nbErrors++
+ else if (request.readyState == 4) {
+ aux = JSON.parse(request.responseText);
+ this.requester.hasSceneChanged = JSON.stringify(aux)!=JSON.stringify(this.requester.sceneJSON);
+ this.requester.sceneJSON = JSON.parse(request.responseText);
+ if (this.requester.hasSceneChanged) this.requester.updateScene();
+ }
+ }
+ request.send();
+ } catch (e) {
+ this.nbErrors++;
+ }
+}
+
+WebGLRenderer.prototype.updateScene = function(){
+ if (typeof(this.sceneJSON) == "undefined" || this.sceneJSON == null) return;
+ c1 = [0,0,0];
+ c2 = [0,0,0];
+ for(l=0; l<this.sceneJSON.Renderers.length; l++){
+ if(this.sceneJSON.Renderers[l].layer==0){
+ this.lookAt = this.sceneJSON.Renderers[l].LookAt;
+ c1 = this.sceneJSON.Renderers[l].Background1;
+ if (typeof(this.sceneJSON.Renderers[l].Background2) != "undefined") c2 = this.sceneJSON.Renderers[l].Background2;
+ }
+ }
+ this.initBackground(c1, c2);
+ if (JSON.stringify(this.oldCamPos)!=JSON.stringify(this.lookAt)){
+ this.translation = [0.0, 0.0, 0.0];
+ this.objScale = 1.0;
+ mat4.identity(this.rotMatrix);
+
+ this.up = [this.lookAt[4], this.lookAt[5], this.lookAt[6]];
+ this.z_dir = [this.lookAt[1]-this.lookAt[7],
+ this.lookAt[2]-this.lookAt[8],
+ this.lookAt[3]-this.lookAt[9]];
+ vec3.normalize(this.z_dir, this.z_dir);
+ vec3.cross(this.z_dir, this.up, this.right);
+ }
+ this.oldCamPos = this.lookAt;
+ var aux = [];
+ intAtServer = false;
+ if (!this.offlineMode){
+ for(w=0; w<this.objects.length; w++){
+ for(j=0; j<this.sceneJSON.Objects.length; j++){
+ if (this.objects[w].md5 == this.sceneJSON.Objects[j].md5 && this.objects[w].id == this.sceneJSON.Objects[j].id){
+ aux[aux.length] = this.objects[w];
+ }
+ }
+ }
+ this.objects = aux;
+
+ for(w=0; w<this.sceneJSON.Objects.length; w++){
+ foundit = false;
+
+ if (this.isCaching){
+ for(j=0; j<this.cachedObjects.length; j++)
+ if (this.cachedObjects[j].md5==this.sceneJSON.Objects[w].md5 &&
+ this.cachedObjects[j].id==this.sceneJSON.Objects[w].id){
+ this.objects[this.objects.length] = this.cachedObjects[j];
+ foundit = true;
+ }
+ }
+ if (!foundit){
+ for(k=0; k<this.sceneJSON.Objects[w].parts; k++){
+ foundit = false;
+ for(j=0; j<this.objects.length; j++){
+ if (this.objects[j].md5==this.sceneJSON.Objects[w].md5 &&
+ this.objects[j].id==this.sceneJSON.Objects[w].id && this.objects[j].part==k+1 )
+ foundit=true;
+ }
+ if(!foundit) this.requestObject(this.sessionId, this.sceneJSON.id, this.sceneJSON.Objects[w].md5,
+ k+1, this.sceneJSON.Objects[w].id, this.sceneJSON.Objects[w].transparency, this.sceneJSON.Objects[w].layer);
+ }
+ }
+ if (this.sceneJSON.Objects[w].interactAtServer==1) intAtServer = true;
+ }
+ }
+ this.hasSceneChanged = false;
+ this.setServerMode(intAtServer);
+}
+
+WebGLRenderer.prototype.requestObject = function(sid, vid, md5, part, id, hastransparency, layer){
+ if (this.offlineMode) return;
+ var request = new XMLHttpRequest();
+ request.requester = this;
+ filename = this.baseURL + "?sid=" + sid + "&vid=" + vid + "&hash=" + md5 + "&part=" + part + "&q=mesh&id=" + id;
+ try {
+ request.open("GET", filename, false);
+ request.overrideMimeType('text/plain; charset=x-user-defined');
+ request.onreadystatechange = function() {
+ if (request.status != 200) this.requester.nbErrors++
+ else if (request.readyState == 4) {
+ foundit = -1;
+ for (i=0; i<this.requester.objects.length; i++)
+ if (this.requester.objects[i].md5 == md5 && this.requester.objects[i].part == part
+ && this.requester.objects[i].id == id) foundit = i;
+ if (foundit == -1){
+ foundit = this.requester.objects.length;
+ this.requester.objects.length++;
+ }
+ this.requester.objects[foundit] = new Object();
+ this.requester.objects[foundit].md5 = md5; //hash
+ this.requester.objects[foundit].part = part; //part
+ this.requester.objects[foundit].sid = sid; //scene id
+ this.requester.objects[foundit].vid = vid; //view id
+ this.requester.objects[foundit].id = id; //object id
+ this.requester.objects[foundit].data = request.responseText;
+ this.requester.objects[foundit].hasTransparency = hastransparency;
+ this.requester.objects[foundit].layer = layer;
+ this.requester.objects[foundit].render = function(){};
+ this.requester.processQueue[this.requester.processQueue.length] = this.requester.objects[foundit];
+ this.requester.cachedObjects[this.requester.cachedObjects.length] = this.requester.objects[foundit];
+ }
+ }
+ request.send();
+ } catch (e){
+ this.nbErrors++;
+ }
+}
+
+WebGLRenderer.prototype.parseObject = function(obj){
+ var ss = []; pos = 0;
+ for(i=0; i<obj.data.length; i++) ss[i] = obj.data.charCodeAt(i) & 0xff;
+
+ size = (ss[pos++]) + (ss[pos++] << 8) + (ss[pos++] << 16) + (ss[pos++] << 24);
+ type = String.fromCharCode(ss[pos++]);
+ obj.type = type;
+ obj.father = this;
+
+ if (type == 'L'){
+ obj.numberOfPoints = (ss[pos++]) + (ss[pos++] << 8) + (ss[pos++] << 16) + (ss[pos++] << 24);
+ //Getting Points
+ test = new Int8Array(obj.numberOfPoints*4*3); for(i=0; i<obj.numberOfPoints*4*3; i++) test[i] = ss[pos++];
+ obj.points = new Float32Array(test.buffer);
+ //Generating Normals
+ test = new Array(obj.numberOfPoints*3); for(i=0; i<obj.numberOfPoints*3; i++) test[i] = 0.0;
+ obj.normals = new Float32Array(test);
+ //Getting Colors
+ test = []; for(i=0; i<obj.numberOfPoints*4; i++) test[i] = ss[pos++]/255.0;
+ obj.colors = new Float32Array(test);
+
+ obj.numberOfIndex = (ss[pos++]) + (ss[pos++] << 8) + (ss[pos++] << 16) + (ss[pos++] << 24);
+ //Getting Index
+ test = new Int8Array(obj.numberOfIndex*2); for(i=0; i<obj.numberOfIndex*2; i++) test[i] = ss[pos++];
+ obj.index = new Uint16Array(test.buffer);
+ //Getting Matrix
+ test = new Int8Array(16*4); for(i=0; i<16*4; i++) test[i] = ss[pos++];
+ obj.matrix = new Float32Array(test.buffer);
+
+ //Creating Buffers
+ obj.lbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.lbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.points, this.gl.STATIC_DRAW); obj.lbuff.itemSize = 3;
+
+ obj.nbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.nbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.normals, this.gl.STATIC_DRAW); obj.nbuff.itemSize = 3;
+
+ obj.cbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.cbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.colors, this.gl.STATIC_DRAW); obj.cbuff.itemSize = 4;
+
+ obj.ibuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, obj.ibuff);
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, obj.index, this.gl.STREAM_DRAW);
+
+ obj.render = this.renderLine;
+ }
+
+ //-=-=-=-=-=[ MESH ]=-=-=-=-=-
+ else if (type == 'M'){
+ obj.numberOfVertices = (ss[pos++]) + (ss[pos++] << 8) + (ss[pos++] << 16) + (ss[pos++] << 24);
+ //Getting Vertices
+ test = new Int8Array(obj.numberOfVertices*4*3); for(i=0; i<obj.numberOfVertices*4*3; i++) test[i] = ss[pos++];
+ obj.vertices = new Float32Array(test.buffer);
+ //Getting Normals
+ test = new Int8Array(obj.numberOfVertices*4*3); for(i=0; i<obj.numberOfVertices*4*3; i++) test[i] = ss[pos++];
+ obj.normals = new Float32Array(test.buffer);
+ //Getting Colors
+ test = []; for(i=0; i<obj.numberOfVertices*4; i++) test[i] = ss[pos++]/255.0;
+ obj.colors = new Float32Array(test);
+
+ obj.numberOfIndex = (ss[pos++]) + (ss[pos++] << 8) + (ss[pos++] << 16) + (ss[pos++] << 24);
+ //Getting Index
+ test = new Int8Array(obj.numberOfIndex*2); for(i=0; i<obj.numberOfIndex*2; i++) test[i] = ss[pos++];
+ obj.index = new Uint16Array(test.buffer);
+ //Getting Matrix
+ test = new Int8Array(16*4); for(i=0; i<16*4; i++) test[i] = ss[pos++];
+ obj.matrix = new Float32Array(test.buffer);
+ //Getting TCoord
+ obj.tcoord = null;
+
+ //Create Buffers
+ obj.vbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.vbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.vertices, this.gl.STATIC_DRAW); obj.vbuff.itemSize = 3;
+
+ obj.nbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.nbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.normals, this.gl.STATIC_DRAW); obj.nbuff.itemSize = 3;
+
+ obj.cbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.cbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.colors, this.gl.STATIC_DRAW); obj.cbuff.itemSize = 4;
+
+ obj.ibuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, obj.ibuff);
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, obj.index, this.gl.STREAM_DRAW);
+
+ obj.render = this.renderMesh;
+ }
+
+ // ColorMap Widget
+ else if (type == 'C'){
+ obj.numOfColors = size;
+
+ //Getting Position
+ test = new Int8Array(2*4); for(i=0; i<2*4; i++) test[i] = ss[pos++];
+ obj.position = new Float32Array(test.buffer);
+
+ //Getting Size
+ test = new Int8Array(2*4); for(i=0; i<2*4; i++) test[i] = ss[pos++];
+ obj.size = new Float32Array(test.buffer);
+
+ //Getting Colors
+ obj.colors = [];
+ for(c=0; c<obj.numOfColors; c++){
+ test = new Int8Array(4); for(i=0; i<4; i++) test[i] = ss[pos++];
+ v = new Float32Array(test.buffer);
+ xrgb = [v[0], ss[pos++], ss[pos++], ss[pos++]];
+ obj.colors[c] = xrgb;
+ }
+
+ obj.orientation = ss[pos++];
+ obj.numOfLabels = ss[pos++];
+ tt = "";
+ for(jj=0; jj<(ss.length-pos); jj++) tt = tt + String.fromCharCode(ss[pos+jj]);
+ obj.title = tt;
+
+ obj.render = this.renderColorMap;
+ }
+
+ // Points
+ else if (type == 'P'){
+ obj.numberOfPoints = (ss[pos++]) + (ss[pos++] << 8) + (ss[pos++] << 16) + (ss[pos++] << 24);
+ //Getting Points
+ test = new Int8Array(obj.numberOfPoints*4*3); for(i=0; i<obj.numberOfPoints*4*3; i++) test[i] = ss[pos++];
+ obj.points = new Float32Array(test.buffer);
+
+ //Getting Colors
+ test = []; for(i=0; i<obj.numberOfPoints*4; i++) test[i] = ss[pos++]/255.0;
+ obj.colors = new Float32Array(test);
+
+ //Getting Matrix //Wendel
+ test = new Int8Array(16*4); for(i=0; i<16*4; i++) test[i] = ss[pos++];
+ obj.matrix = new Float32Array(test.buffer);
+
+ //Creating Buffers
+ obj.pbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.pbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.points, this.gl.STATIC_DRAW); obj.pbuff.itemSize = 3;
+
+ obj.cbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obj.cbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, obj.colors, this.gl.STATIC_DRAW); obj.cbuff.itemSize = 4;
+
+ obj.render = this.renderPoints;
+ }
+}
+
+WebGLRenderer.prototype.renderColorMap = function(){
+ obj = this;
+ render = this.father;
+
+ range = [obj.colors[0][0], obj.colors[obj.colors.length-1][0]];
+ size = [obj.size[0]*render.view.width, obj.size[1]*render.view.height];
+ pos = [obj.position[0]*render.view.width, (1-obj.position[1])*render.view.height];
+ pos[1] = pos[1]-size[1];
+ dx = size[0]/size[1];
+ dy = size[1]/size[0];
+ realSize = size;
+
+ textSizeX = Math.round(render.view.height/35);
+ textSizeY = Math.round(render.view.height/23);
+ if (obj.orientation == 1){
+ size[0] = size[0]*dy/25;
+ size[1] = size[1]-(2*textSizeY);
+ } else {
+ size[0] = size[0];
+ size[1] = size[1]*dx/25;
+ }
+
+ // Draw Gradient
+ ctx = this.father.ctx2d;
+ if(obj.orientation == 1){
+ pos[1] += 2*textSizeY;
+ grad = ctx.createLinearGradient(pos[0], pos[1], pos[0], pos[1]+size[1]);
+ } else {
+ pos[1] += 2*textSizeY;
+ grad = ctx.createLinearGradient(pos[0], pos[1], pos[0]+size[0], pos[1]);
+ }
+ if ((range[1]-range[0]) == 0){
+ color = 'rgba(' + obj.colors[0][1] + ',' + obj.colors[0][2] + ',' + obj.colors[0][3] + ',1)';
+ grad.addColorStop(0, color);
+ grad.addColorStop(1, color);
+ } else {
+ for(c=0; c<obj.colors.length; c++){
+ v = ((obj.colors[c][0]-range[0])/(range[1]-range[0]));
+ if (obj.orientation == 1) v=1-v;
+ color = 'rgba(' + obj.colors[c][1] + ',' + obj.colors[c][2] + ',' + obj.colors[c][3] + ',1)';
+ grad.addColorStop(v, color);
+ }
+ }
+ ctx.fillStyle = grad;
+ ctx.fillRect(pos[0], pos[1], size[0], size[1]);
+ // Draw Range Labels
+ range[0] = Math.round(range[0]*1000)/1000;
+ range[1] = Math.round(range[1]*1000)/1000;
+ ctx.fillStyle = 'white';
+ ctx.font = textSizeY + 'px sans-serif';
+ ctx.txtBaseline = 'ideographic';
+ if (obj.orientation == 1){
+ ctx.fillText(range[1], pos[0], pos[1]-5);
+ ctx.fillText(range[0], pos[0], pos[1]+size[1]+textSizeY);
+ } else {
+ ctx.fillText(range[0], pos[0], pos[1]+size[1]+textSizeY);
+ txt = range[1].toString();
+ ctx.fillText(range[1], pos[0]+size[0]-((txt.length-1)*textSizeX), pos[1]+size[1]+textSizeY);
+ }
+ // Draw Title
+ ctx.fillStyle = 'white';
+ ctx.font = textSizeY + 'px sans-serif';
+ ctx.txtBaseline = 'ideographic';
+ if (obj.orientation == 1) ctx.fillText(obj.title, pos[0]+(obj.size[0]*render.view.width)/2-(obj.title.length*textSizeX/2), pos[1]-textSizeY-5);
+ else ctx.fillText(obj.title, pos[0]+size[0]/2-(obj.title.length*textSizeX/2), pos[1]-textSizeY-5);
+ // Draw Intervals' line
+ //Draw Interval make the render process slow
+ /*
+ interval = obj.numOfLabels-1;
+ if (obj.orientation == 1){
+ diff = size[1]/(interval-1);
+ y = pos[1]+size[1];
+ x = size[0]/2;
+ for(ii=0; ii<interval; ii++){
+ y = Math.floor(y) + 0.5;
+ if (ii%5) ctx.moveTo(pos[0]+2*x, y);
+ else ctx.moveTo(pos[0]+x, y);
+ ctx.lineTo(pos[0]+x*3, y);
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = "white";
+ ctx.stroke();
+ y -= diff;
+ }
+ } else {
+ diff = size[0]/(interval-1);
+ y = size[1]/2;
+ x = pos[0];
+ for(ii=0; ii<interval; ii++){
+ x = Math.floor(x) + 0.5;
+ if (ii%5) ctx.moveTo(x, pos[1]);
+ else ctx.moveTo(x, pos[1]+y);
+ ctx.lineTo(x, pos[1]-y);
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = "white";
+ ctx.stroke();
+ x += diff;
+ }
+ }/**/
+}
+
+WebGLRenderer.prototype.initBackground = function(c1, c2){
+ if (typeof(this.gl) == "undefined") return;
+ if (typeof(this.sceneJSON) == "undefined") return;
+
+ this.background = new Object();
+ this.background.vertices = new Float32Array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0]);
+ this.background.colors = new Float32Array([c1[0], c1[1], c1[2], 1.0,
+ c1[0], c1[1], c1[2], 1.0,
+ c2[0], c2[1], c2[2], 1.0,
+ c2[0], c2[1], c2[2], 1.0]);
+ this.background.index = new Uint16Array([0, 1, 2, 0, 2, 3]);
+ this.background.normals = new Float32Array([0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0]);
+
+ this.background.numberOfIndex = 6;
+
+ //Create Buffers
+ this.background.vbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.background.vbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.background.vertices, this.gl.STATIC_DRAW); this.background.vbuff.itemSize = 3;
+ this.background.nbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.background.nbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.background.normals, this.gl.STATIC_DRAW); this.background.nbuff.itemSize = 3;
+ this.background.cbuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.background.cbuff);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.background.colors, this.gl.STATIC_DRAW); this.background.cbuff.itemSize = 4;
+ this.background.ibuff = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.background.ibuff);
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, this.background.index, this.gl.STREAM_DRAW);
+}
+
+WebGLRenderer.prototype.renderBackground = function(){
+ if (this.background == null) return;
+
+ this.gl.useProgram(this.shaderProgram);
+ this.gl.uniform1i(this.shaderProgram.uIsLine, false);
+
+ mat4.translate(this.mvMatrix, [0.0, 0.0, -1.0]);
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.background.vbuff);
+ this.gl.vertexAttribPointer(this.shaderProgram.vertexPositionAttribute, this.background.vbuff.itemSize, this.gl.FLOAT, false, 0, 0);
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.background.nbuff);
+ this.gl.vertexAttribPointer(this.shaderProgram.vertexNormalAttribute, this.background.nbuff.itemSize, this.gl.FLOAT, false, 0, 0);
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.background.cbuff);
+ this.gl.vertexAttribPointer(this.shaderProgram.vertexColorAttribute, this.background.cbuff.itemSize, this.gl.FLOAT, false, 0, 0);
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.background.ibuff);
+ this.setMatrixUniforms(this.shaderProgram);
+ this.gl.drawElements(this.gl.TRIANGLES, this.background.numberOfIndex, this.gl.UNSIGNED_SHORT, 0);
+}
+
+WebGLRenderer.prototype.renderMesh = function(){
+ obj = this;
+ render = this.father;
+ render.gl.useProgram(render.shaderProgram);
+
+ render.gl.uniform1i(render.shaderProgram.uIsLine, false);
+
+ cameraRot = mat4.toRotationMat(render.mvMatrix);
+ mat4.transpose(cameraRot);
+ inverse = mat4.create(); mat4.inverse(cameraRot, inverse);
+ test = mat4.create(obj.matrix);
+ mat4.transpose(test);
+
+ icenter = [-render.sceneJSON.Center[0], -render.sceneJSON.Center[1], -render.sceneJSON.Center[2]];
+
+ mvPushMatrix(render.mvMatrix);
+ mat4.multiply(render.mvMatrix, cameraRot, render.mvMatrix);
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, render.translation);
+ mat4.multiply(render.mvMatrix, inverse, render.mvMatrix);
+
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, render.sceneJSON.Center);
+ mat4.multiply(render.mvMatrix, cameraRot, render.mvMatrix);
+ if(obj.layer == 0) mat4.scale(render.mvMatrix, [render.objScale, render.objScale, render.objScale], render.mvMatrix);
+ mat4.multiply(render.mvMatrix, render.rotMatrix, render.mvMatrix);
+ mat4.multiply(render.mvMatrix, inverse, render.mvMatrix);
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, icenter);
+
+ render.rotMatrix2 = render.mvMatrix;
+
+ mat4.multiply(render.mvMatrix, test, render.mvMatrix);
+
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.vbuff);
+ render.gl.vertexAttribPointer(render.shaderProgram.vertexPositionAttribute, obj.vbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.nbuff);
+ render.gl.vertexAttribPointer(render.shaderProgram.vertexNormalAttribute, obj.nbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.cbuff);
+ render.gl.vertexAttribPointer(render.shaderProgram.vertexColorAttribute, obj.cbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ELEMENT_ARRAY_BUFFER, obj.ibuff);
+ render.setMatrixUniforms(render.shaderProgram);
+ render.gl.drawElements(render.gl.TRIANGLES, obj.numberOfIndex, render.gl.UNSIGNED_SHORT, 0);
+ render.mvMatrix = mvPopMatrix();
+}
+
+WebGLRenderer.prototype.renderLine = function(){
+ obj = this;
+ render = this.father;
+ render.gl.useProgram(render.shaderProgram);
+
+ render.gl.enable(render.gl.POLYGON_OFFSET_FILL); //Avoid zfighting
+ render.gl.polygonOffset(-1.0, -1.0);
+
+ render.gl.uniform1i(render.shaderProgram.uIsLine, true);
+
+ cameraRot = mat4.toRotationMat(render.mvMatrix);
+ mat4.transpose(cameraRot);
+ inverse = mat4.create(); mat4.inverse(cameraRot, inverse);
+ test = mat4.create(obj.matrix);
+ mat4.transpose(test);
+
+ icenter = [-render.sceneJSON.Center[0], -render.sceneJSON.Center[1], -render.sceneJSON.Center[2]];
+
+ mvPushMatrix(render.mvMatrix);
+ mat4.multiply(render.mvMatrix, cameraRot, render.mvMatrix);
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, render.translation);
+ mat4.multiply(render.mvMatrix, inverse, render.mvMatrix);
+
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, render.sceneJSON.Center);
+ mat4.multiply(render.mvMatrix, cameraRot, render.mvMatrix);
+ if(obj.layer == 0) mat4.scale(render.mvMatrix, [render.objScale, render.objScale, render.objScale], render.mvMatrix);
+ mat4.multiply(render.mvMatrix, render.rotMatrix, render.mvMatrix);
+ mat4.multiply(render.mvMatrix, inverse, render.mvMatrix);
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, icenter);
+
+ render.rotMatrix2 = render.mvMatrix;
+
+ mat4.multiply(render.mvMatrix, test, render.mvMatrix);
+
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.lbuff);
+ render.gl.vertexAttribPointer(render.shaderProgram.vertexPositionAttribute, obj.lbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.nbuff);
+ render.gl.vertexAttribPointer(render.shaderProgram.vertexNormalAttribute, obj.nbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.cbuff);
+ render.gl.vertexAttribPointer(render.shaderProgram.vertexColorAttribute, obj.cbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ELEMENT_ARRAY_BUFFER, obj.ibuff);
+ render.setMatrixUniforms(render.shaderProgram);
+ render.gl.drawElements(render.gl.LINES, obj.numberOfIndex, render.gl.UNSIGNED_SHORT, 0);
+ render.mvMatrix = mvPopMatrix();
+
+ render.gl.disable(render.gl.POLYGON_OFFSET_FILL);
+}
+
+WebGLRenderer.prototype.renderPoints = function(){
+ obj = this;
+ render = this.father;
+ render.gl.useProgram(render.pointShaderProgram);
+
+ render.gl.enable(render.gl.POLYGON_OFFSET_FILL); //Avoid zfighting
+ render.gl.polygonOffset(-1.0, -1.0);
+
+ render.gl.uniform1f(render.pointShaderProgram.uPointSize, 2.0);//Wendel
+
+ cameraRot = mat4.toRotationMat(render.mvMatrix);
+ mat4.transpose(cameraRot);
+ inverse = mat4.create(); mat4.inverse(cameraRot, inverse);
+ test = mat4.create(obj.matrix);
+ mat4.transpose(test);
+
+ icenter = [-render.sceneJSON.Center[0], -render.sceneJSON.Center[1], -render.sceneJSON.Center[2]];
+
+ mvPushMatrix(render.mvMatrix);
+ mat4.multiply(render.mvMatrix, cameraRot, render.mvMatrix);
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, render.translation);
+ mat4.multiply(render.mvMatrix, inverse, render.mvMatrix);
+
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, render.sceneJSON.Center);
+ mat4.multiply(render.mvMatrix, cameraRot, render.mvMatrix);
+ if(obj.layer == 0) mat4.scale(render.mvMatrix, [render.objScale, render.objScale, render.objScale], render.mvMatrix);
+ mat4.multiply(render.mvMatrix, render.rotMatrix, render.mvMatrix);
+ mat4.multiply(render.mvMatrix, inverse, render.mvMatrix);
+ if(obj.layer == 0) mat4.translate(render.mvMatrix, icenter);
+
+ render.rotMatrix2 = render.mvMatrix;
+
+ mat4.multiply(render.mvMatrix, test, render.mvMatrix);
+
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.pbuff);
+ render.gl.vertexAttribPointer(render.pointShaderProgram.vertexPositionAttribute, obj.pbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.gl.bindBuffer(render.gl.ARRAY_BUFFER, obj.cbuff);
+ render.gl.vertexAttribPointer(render.pointShaderProgram.vertexColorAttribute, obj.cbuff.itemSize, render.gl.FLOAT, false, 0, 0);
+ render.setMatrixUniforms(render.pointShaderProgram);
+ render.gl.drawArrays(render.gl.POINTS, 0, obj.numberOfPoints);//Wendel
+ render.mvMatrix = mvPopMatrix();
+
+ render.gl.disable(render.gl.POLYGON_OFFSET_FILL);
+}
+
+WebGLRenderer.prototype.setMatrixUniforms = function(s) {
+ mvMatrixInv = mat4.create();
+ normal = mat4.create();
+ mat4.inverse(this.mvMatrix, mvMatrixInv);
+ mat4.transpose(mvMatrixInv, normal);
+
+ this.gl.uniformMatrix4fv(s.pMatrixUniform, false, this.pMatrix);
+ this.gl.uniformMatrix4fv(s.mvMatrixUniform, false, this.mvMatrix);
+ if(s.nMatrixUniform != null) this.gl.uniformMatrix4fv(s.nMatrixUniform, false, normal);
+}
+
+WebGLRenderer.prototype.processObject = function() {
+ if (this.processQueue.length != 0){
+ obj = this.processQueue[this.processQueue.length-1];
+ this.processQueue.length -= 1;
+ this.parseObject(obj);
+ }
+}
+
+WebGLRenderer.prototype.drawScene = function() {
+ this.drawInterval = requestAnimFrame(new Function("webglRenderers['" + this.view.id + "'].drawScene();"));
+ if (this.hasSceneChanged){
+ this.updateScene();
+ }
+ if (this.sceneJSON == null){
+ return;
+ }
+ this.frames++;
+
+ if(this.frames >= 50 && this.nbErrors < 5){
+ this.frames = 0;
+ ko = new Date();
+ currTime = ko.getTime();
+ diff = currTime - this.lastTime;
+ this.lastTime = currTime;
+ this.fps = 50000/diff;
+ }
+ this.processObject();
+
+ this.gl.viewport(0, 0, this.gl.viewportWidth, this.gl.viewportHeight);
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
+
+ mat4.ortho(-1.0, 1.0, -1.0, 1.0, 1.0, 1000000.0, this.pMatrix);
+ mat4.identity(this.mvMatrix);
+ this.gl.disable(this.gl.DEPTH_TEST);
+ this.renderBackground();
+ this.gl.enable(this.gl.DEPTH_TEST);
+
+ this.ctx2d.clearRect(0, 0, this.view.width, this.view.height);
+ for(rr=this.sceneJSON.Renderers.length-1; rr>=0 ; rr--){
+ renderer = this.sceneJSON.Renderers[rr];
+ width = renderer.size[0]-renderer.origin[0];
+ height = renderer.size[1]-renderer.origin[1];
+ width = width*this.view.width;
+ height = height*this.view.height;
+ x = renderer.origin[0]*this.view.width;
+ y = renderer.origin[1]*this.view.height;
+ if (y < 0) y = 0;
+ this.gl.viewport(x, y, width, height);
+ //this.gl.clear(this.gl.DEPTH_BUFFER_BIT);
+ mat4.perspective(renderer.LookAt[0], width/height, 0.1, 1000000.0, this.pMatrix);
+ mat4.identity(this.mvMatrix);
+ mat4.lookAt([renderer.LookAt[7], renderer.LookAt[8], renderer.LookAt[9]],
+ [renderer.LookAt[1], renderer.LookAt[2], renderer.LookAt[3]],
+ [renderer.LookAt[4], renderer.LookAt[5], renderer.LookAt[6]],
+ this.mvMatrix);
+
+ for(r=0; r<this.objects.length; r++){
+ if (!this.objects[r].hasTransparency && this.objects[r].layer == rr) this.objects[r].render();
+ }
+ //Render Objects with Transparency
+ this.gl.enable(this.gl.BLEND); //Enable transparency
+ this.gl.enable(this.gl.POLYGON_OFFSET_FILL); //Avoid zfighting
+ this.gl.polygonOffset(-1.0, -1.0);
+ for(r=0; r<this.objects.length; r++){
+ if (this.objects[r].hasTransparency && this.objects[r].layer == rr) this.objects[r].render();
+ }
+ this.gl.disable(this.gl.POLYGON_OFFSET_FILL);
+ this.gl.disable(this.gl.BLEND);
+ }
+}
+
+WebGLRenderer.prototype.initShaders = function() {
+ this.shaderProgram;
+ this.pointShaderProgram;
+
+ var fragmentShader = this.getShader("shader-fs");
+ var vertexShader = this.getShader("shader-vs");
+ var pointFragShader = this.getShader("shader-fs-Point");
+ var pointVertShader = this.getShader("shader-vs-Point");
+
+ this.shaderProgram = this.gl.createProgram();
+ this.gl.attachShader(this.shaderProgram, vertexShader);
+ this.gl.attachShader(this.shaderProgram, fragmentShader);
+ this.gl.linkProgram(this.shaderProgram);
+ if (!this.gl.getProgramParameter(this.shaderProgram, this.gl.LINK_STATUS)) {
+ alert("Could not initialise shaders");
+ }
+ this.pointShaderProgram = this.gl.createProgram();
+ this.gl.attachShader(this.pointShaderProgram, pointVertShader);
+ this.gl.attachShader(this.pointShaderProgram, pointFragShader);
+ this.gl.linkProgram(this.pointShaderProgram);
+ if (!this.gl.getProgramParameter(this.pointShaderProgram, this.gl.LINK_STATUS)) {
+ alert("Could not initialise the point shaders");
+ }
+
+ this.gl.useProgram(this.pointShaderProgram);
+ this.pointShaderProgram.vertexPositionAttribute = this.gl.getAttribLocation(this.pointShaderProgram, "aVertexPosition");
+ this.gl.enableVertexAttribArray(this.pointShaderProgram.vertexPositionAttribute);
+ this.pointShaderProgram.vertexColorAttribute = this.gl.getAttribLocation(this.pointShaderProgram, "aVertexColor");
+ this.gl.enableVertexAttribArray(this.pointShaderProgram.vertexColorAttribute);
+ this.pointShaderProgram.pMatrixUniform = this.gl.getUniformLocation(this.pointShaderProgram, "uPMatrix");
+ this.pointShaderProgram.mvMatrixUniform = this.gl.getUniformLocation(this.pointShaderProgram, "uMVMatrix");
+ this.pointShaderProgram.nMatrixUniform = this.gl.getUniformLocation(this.pointShaderProgram, "uNMatrix");
+ this.pointShaderProgram.uPointSize = this.gl.getUniformLocation(this.pointShaderProgram, "uPointSize");
+
+ this.gl.useProgram(this.shaderProgram);
+ this.shaderProgram.vertexPositionAttribute = this.gl.getAttribLocation(this.shaderProgram, "aVertexPosition");
+ this.gl.enableVertexAttribArray(this.shaderProgram.vertexPositionAttribute);
+ this.shaderProgram.vertexColorAttribute = this.gl.getAttribLocation(this.shaderProgram, "aVertexColor");
+ this.gl.enableVertexAttribArray(this.shaderProgram.vertexColorAttribute);
+ this.shaderProgram.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgram, "aVertexNormal");
+ this.gl.enableVertexAttribArray(this.shaderProgram.vertexNormalAttribute);
+ this.shaderProgram.pMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "uPMatrix");
+ this.shaderProgram.mvMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "uMVMatrix");
+ this.shaderProgram.nMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "uNMatrix");
+ this.shaderProgram.uIsLine = this.gl.getUniformLocation(this.shaderProgram, "uIsLine");
+}
+
+WebGLRenderer.prototype.getShader = function(id) {
+ var shaderScript = document.getElementById(id);
+ if (!shaderScript) {
+ return null;
+ }
+
+ var str = "";
+ var k = shaderScript.firstChild;
+ while (k) {
+ if (k.nodeType == 3) {
+ str += k.textContent;
+ }
+ k = k.nextSibling;
+ }
+
+ var shader;
+ if (shaderScript.type == "x-shader/x-fragment") {
+ shader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+ } else if (shaderScript.type == "x-shader/x-vertex") {
+ shader = this.gl.createShader(this.gl.VERTEX_SHADER);
+ } else {
+ return null;
+ }
+
+ this.gl.shaderSource(shader, str);
+ this.gl.compileShader(shader);
+
+ if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
+ alert(this.gl.getShaderInfoLog(shader));
+ return null;
+ }
+
+ return shader;
+}
+
+WebGLRenderer.prototype.forceUpdateCamera = function(){
+ if (typeof(this.gl) == "undefined" || this.gl == null || renderers.current != this) return;
+ if (this.offlineMode) return;
+ render = this;
+
+ pos = [render.lookAt[7], render.lookAt[8], render.lookAt[9]];
+ up = [render.lookAt[4], render.lookAt[5], render.lookAt[6]];
+ fp = [render.lookAt[1], render.lookAt[2], render.lookAt[3]];
+ tt = [render.translation[0], render.translation[1], 0.0];
+ center = [render.sceneJSON.Center[0], render.sceneJSON.Center[1], render.sceneJSON.Center[2]];
+
+ cameraRot = mat4.toRotationMat(render.mvMatrix);
+ mat4.transpose(cameraRot);
+ inverse = mat4.create(); mat4.inverse(cameraRot, inverse);
+
+ inv = mat4.create();
+ mat4.identity(inv);
+ mat4.multiply(inv, cameraRot, inv);
+ mat4.scale(inv, [render.objScale, render.objScale, render.objScale], inv);
+ mat4.multiply(inv, render.rotMatrix, inv);
+ mat4.multiply(inv, inverse, inv);
+
+ mat4.inverse(inv, inv);
+ fp = vec3.subtract(fp, center, fp);
+ pos = vec3.subtract(pos, center, pos);
+ mat4.multiplyVec3(inv, fp, fp);
+ mat4.multiplyVec3(inv, pos, pos);
+ mat4.multiplyVec3(inv, up, up);
+ fp = vec3.add(fp, center, fp);
+ pos = vec3.add(pos, center, pos);
+ vec3.normalize(up, up);
+
+ tt2 = [0, 0, 0];
+ tt2[0] += tt[0]*render.right[0];
+ tt2[1] += tt[0]*render.right[1];
+ tt2[2] += tt[0]*render.right[2];
+ tt2[0] += tt[1]*render.up[0];
+ tt2[1] += tt[1]*render.up[1];
+ tt2[2] += tt[1]*render.up[2];
+
+ vec3.subtract(pos, tt2, pos);
+ vec3.subtract(fp , tt2, fp);
+
+ paraviewObjects[parseInt(render.sessionId)].sendEvent("UpdateCamera", render.viewId + " " + fp[0] + " " + fp[1] + " " + fp[2]
+ + " " + up[0] + " " + up[1] + " " + up[2] + " " + pos[0] + " " + pos[1] + " " + pos[2]);
+ clearTimeout(this.updateId);
+ this.updateId = setTimeout("webglRenderers[\'" + this.view.id + "\'].updateCamera()", this.updateInterval);
+}
+
+WebGLRenderer.prototype.updateCamera = function(){
+ if (!this.mouseDown){
+ this.updateId = setTimeout("webglRenderers[\'" + this.view.id + "\'].updateCamera()", this.updateInterval);
+ return;
+ }
+ if (this.serverMode) return;
+ this.forceUpdateCamera();
+}
+
+/**********************************************************************************/
+
+var mvMatrixStack = [];
+function mvPushMatrix(m) {
+ var copy = mat4.create();
+ mat4.set(m, copy);
+ mvMatrixStack.push(copy);
+}
+
+function mvPopMatrix() {
+ if (mvMatrixStack.length == 0) {
+ throw "Invalid popMatrix!";
+ }
+ return mvMatrixStack.pop();
+}
+
+
+function handleMouseDown(event, id) {
+ render = webglRenderers[id];
+ render.mouseDown = true;
+ render.lastMouseX = event.clientX;
+ render.lastMouseY = event.clientY;
+ if (!render.offlineMode){
+ paraviewObjects[render.sessionId].sendEvent('MouseEvent', render.viewId + ' 0 0');
+ updateRendererSize(render.sessionId, render.viewId, render.view.width/render.interactionRatio, render.view.height/render.interactionRatio);
+ }
+ event.preventDefault();
+ return false;
+}
+
+function handleMouseUp(event, id) {
+ render = webglRenderers[id];
+ render.mouseDown = false;
+ if (!render.offlineMode){
+ paraviewObjects[render.sessionId].sendEvent('MouseEvent', render.viewId + ' 2 0');
+ updateRendererSize(render.sessionId, render.viewId, render.view.width, render.view.height);
+ render.forceUpdateCamera();
+ render.requestMetaData();
+ }
+ event.preventDefault();
+}
+
+function handleMouseMove(event, id) {
+ render = webglRenderers[id];
+ if (!render.mouseDown) {
+ return;
+ }
+ var newX = event.clientX;
+ var newY = event.clientY;
+ var deltaX = newX - render.lastMouseX;
+ var deltaY = newY - render.lastMouseY;
+
+ if (event.button == 0){
+ var rX = deltaX/50.0;
+ var rY = deltaY/50.0;
+ var mx = mat4.create(); mat4.identity(mx); mat4.rotate(mx, rX, [0, 1, 0]);
+ var my = mat4.create(); mat4.identity(my); mat4.rotate(my, rY, [1, 0, 0]);
+ mat4.multiply(mx, my, mx);
+ mat4.multiply(mx, render.rotMatrix, render.rotMatrix);
+ } else if (event.button == 1){
+ z = Math.abs(render.sceneJSON.Renderers[0].LookAt[9]-render.sceneJSON.Renderers[0].LookAt[3]);
+ aux = z/render.objScale;
+ render.translation[0] += aux*deltaX/1500.0;
+ render.translation[1] -= aux*deltaY/1500.0;
+ } else if (event.button == 2){
+ render.objScale += render.objScale*(deltaY)/200.0;
+ } else {
+ render.objScale += render.objScale*(deltaY)/200.0;
+ }
+
+ render.lastMouseX = newX;
+ render.lastMouseY = newY;
+
+ event.preventDefault();
+}
+
+function mouseServerInt(rendererId, sessionId, viewId, action, event){
+ consumeEvent(event);
+ render = webglRenderers[rendererId];
+ render.interaction.lastRealEvent = event;
+ var width = render.view.width;
+ var height = render.view.height;
+
+ if(action == 'down') {
+ if(render.interaction.needUp) {
+ paraviewObjects[sessionId].sendEvent('MouseEvent', viewId + ' 2 ' + render.interaction.lastEvent);
+ }
+ render.interaction.isDragging = true;
+ render.interaction.needUp = true;
+ switch(event.button){
+ case 1:
+ render.interaction.button = '0 ';
+ break;
+ case 4:
+ render.interaction.button = '1 ';
+ break;
+ case 2:
+ render.interaction.button = '2 ';
+ break;
+ }
+ render.interaction.action = " 0 ";
+ render.interaction.keys = "";
+ if(event.ctrlKey) {
+ render.interaction.keys += "1";
+ } else {
+ render.interaction.keys += "0";
+ }
+ if(event.shiftKey) {
+ render.interaction.keys += " 1";
+ } else {
+ render.interaction.keys += " 0";
+ }
+ render.interaction.x = event.screenX;
+ render.interaction.y = event.screenY;
+
+ // Keep relative origin
+ var docX = event.pageX;
+ var docY = event.pageY;
+ render.interaction.xOrigin = docX - render.getPageX();
+ render.interaction.yOrigin = docY - render.getPageY();
+ } else if (action == 'move') {
+ render.interaction.action = " 1 ";
+ } else if ( action == 'up' || action == 'click') {
+ render.interaction.isDragging = false;
+ render.interaction.needUp = false;
+ render.interaction.action = " 2 ";
+ //
+ var mouseInfo = ((event.screenX-render.interaction.x + render.interaction.xOrigin)/height) + " " + (1-(event.screenY-render.interaction.y + render.interaction.yOrigin)/height) + " " + render.interaction.keys ;
+ render.interaction.lastEvent = render.interaction.button + mouseInfo;
+ paraviewObjects[sessionId].sendEvent('MouseEvent', viewId + render.interaction.action + render.interaction.lastEvent);
+ render.interaction.scale = 1;
+ render.interaction.button = event.button + ' ';
+ render.interaction.keys = "0 0"
+ }
+ if(render.interaction.isDragging ){
+ var mouseInfoDrag = ((event.screenX-render.interaction.x + render.interaction.xOrigin)/height) + " " + (1-(event.screenY-render.interaction.y + render.interaction.yOrigin)/height) + " " + render.interaction.keys ;
+ var mouseAction = 'MouseEvent';
+ if(action == 'move') {
+ mouseAction = 'MouseMove';
+ }
+ render.interaction.lastEvent = render.interaction.button + mouseInfoDrag;
+ paraviewObjects[sessionId].sendEvent(mouseAction, viewId + render.interaction.action + render.interaction.lastEvent);
+ }
+ return false;
+}